summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorPierre Pronchery <khorben@FreeBSD.org>2026-06-24 19:47:19 +0000
committerPierre Pronchery <khorben@FreeBSD.org>2026-06-29 05:21:29 +0000
commitebcc78644e0cbc4141807c0887ef8d902cb91bb4 (patch)
treeda5efe7c3c9ae0f140e24a1b69833a476fb09f01 /tests
parent6294b6ab217a2d5f1d2bc23a64505a228294c508 (diff)
Diffstat (limited to 'tests')
-rw-r--r--tests/Kyuafile.in14
-rw-r--r--tests/api/test-api.h186
-rw-r--r--tests/api/test-audit.c171
-rw-r--r--tests/api/test-buffer.c417
-rw-r--r--tests/api/test-bytecode.c643
-rw-r--r--tests/api/test-client.c334
-rw-r--r--tests/api/test-dependency.c551
-rw-r--r--tests/api/test-fileio.c236
-rw-r--r--tests/api/test-fragment.c376
-rw-r--r--tests/api/test-license.c311
-rw-r--r--tests/api/test-path-utils.c119
-rw-r--r--tests/api/test-tuple.c258
-rw-r--r--tests/api/test-variable.c247
-rwxr-xr-xtests/basic.sh391
-rwxr-xr-xtests/builtins.sh68
-rwxr-xr-xtests/conflicts.sh23
-rwxr-xr-xtests/framework.sh20
-rw-r--r--tests/lib-sbom-files/basic-spdx-base-id.json90
-rw-r--r--tests/lib-sbom-files/basic-use-uri.json90
-rw-r--r--tests/lib-sbom-files/basic.json90
-rw-r--r--tests/lib-sbom-files/define_variable.json90
-rw-r--r--tests/lib-sbom-files/define_variable_with_dependency.json162
-rw-r--r--tests/lib-sbom-files/meta_package-use-uri-spdx-base-id.json544
-rw-r--r--tests/lib-sbom-files/meta_package.json544
-rw-r--r--tests/lib-sbom-files/with_dependency.json162
-rw-r--r--tests/lib-sbom/meta_package.pc12
-rw-r--r--tests/lib-sbom/test1.pc6
-rw-r--r--tests/lib-sbom/test2.pc7
-rw-r--r--tests/lib-sbom/test3.pc8
-rw-r--r--tests/lib-sbom/test4.pc8
-rw-r--r--tests/lib-sbom/test5.pc7
-rw-r--r--tests/lib-sbom/test6.pc7
-rw-r--r--tests/lib-sbom/variable-test1.pc7
-rw-r--r--tests/lib-sbom/variable-test2.pc7
-rw-r--r--tests/lib1/c-comment.pc1
-rw-r--r--tests/lib1/cflags-shared-test.pc12
-rw-r--r--tests/lib1/dollar-sign-escape-2.pc7
-rw-r--r--tests/lib1/dollar-sign-escape.pc7
-rw-r--r--tests/lib1/duplicate-tuple.pc6
-rw-r--r--tests/lib1/flag-whitespace-2.pc5
-rw-r--r--tests/lib1/flag-whitespace.pc5
-rw-r--r--tests/lib1/foo.pc2
-rw-r--r--tests/lib1/foobar.pc1
-rw-r--r--tests/lib1/fragment-group-c.pc2
-rw-r--r--tests/lib1/framework-with-libs.pc4
-rw-r--r--tests/lib1/idirafter-ordering.pc2
-rw-r--r--tests/lib1/isystem-sysroot-prefixed.pc4
-rw-r--r--tests/lib1/libs-shared-test.pc10
-rw-r--r--tests/lib1/license-malformed.pc10
-rw-r--r--tests/lib1/link-abi-cxx.pc5
-rw-r--r--tests/lib1/link-abi-multi.pc5
-rw-r--r--tests/lib1/link-abi-plainc.pc4
-rw-r--r--tests/lib1/link-abi-private.pc5
-rw-r--r--tests/lib1/link-abi-public.pc5
-rw-r--r--tests/lib1/multiline-folding.pc9
-rw-r--r--tests/lib1/requires-shared-dep.pc10
-rw-r--r--tests/lib1/requires-shared.pc11
-rw-r--r--tests/lib1/utf8.pc10
-rw-r--r--tests/lib1/variable-fragment-expansion.pc7
-rw-r--r--tests/meson.build23
-rwxr-xr-xtests/parser.sh372
-rw-r--r--tests/personality-data/i386-linux-gnu.personality4
-rwxr-xr-xtests/provides.sh310
-rwxr-xr-xtests/regress.sh354
-rwxr-xr-xtests/requires.sh176
-rwxr-xr-xtests/symlink.sh122
-rwxr-xr-xtests/sysroot.sh110
-rw-r--r--tests/test-runner.c1576
-rw-r--r--tests/test_env.sh.in50
-rwxr-xr-xtests/version.sh38
-rw-r--r--tests/win-shim.h57
71 files changed, 7464 insertions, 2083 deletions
diff --git a/tests/Kyuafile.in b/tests/Kyuafile.in
deleted file mode 100644
index f50b749fd717..000000000000
--- a/tests/Kyuafile.in
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax(2)
-
-test_suite('pkgconf')
-
-atf_test_program{name='basic'}
-atf_test_program{name='requires'}
-atf_test_program{name='regress'}
-atf_test_program{name='parser'}
-atf_test_program{name='sysroot'}
-atf_test_program{name='conflicts'}
-atf_test_program{name='version'}
-atf_test_program{name='framework'}
-atf_test_program{name='provides'}
-atf_test_program{name='symlink'}
diff --git a/tests/api/test-api.h b/tests/api/test-api.h
new file mode 100644
index 000000000000..cbe6598c846e
--- /dev/null
+++ b/tests/api/test-api.h
@@ -0,0 +1,186 @@
+/*
+ * test-api.h
+ * test API macros and functions.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#ifndef TEST_API_H
+#define TEST_API_H
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/path.h>
+#include <tests/win-shim.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpragmas" // older gcc may not know the next one
+ // Stop false-positive warnings for test cases
+# pragma GCC diagnostic ignored "-Wanalyzer-tainted-assertion"
+#endif
+
+#define TEST_FAIL_(fmt, ...) \
+ do { \
+ fprintf(stderr, "FAIL: %s:%d: " fmt "\n", \
+ __FILE__, __LINE__, __VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+#define TEST_ASSERT_TRUE(expr) \
+ do { \
+ if (!(expr)) \
+ TEST_FAIL_("TEST_ASSERT_TRUE(%s) was false", #expr); \
+ } while (0)
+
+#define TEST_ASSERT_FALSE(expr) \
+ do { \
+ if ((expr)) \
+ TEST_FAIL_("TEST_ASSERT_FALSE(%s) was true", #expr); \
+ } while (0)
+
+#define TEST_ASSERT_NULL(expr) \
+ do { \
+ const void *_v = (const void *)(expr); \
+ if (_v != NULL) \
+ TEST_FAIL_("TEST_ASSERT_NULL(%s) was %p", #expr, _v); \
+ } while (0)
+
+#define TEST_ASSERT_NONNULL(expr) \
+ do { \
+ if ((expr) == NULL) \
+ TEST_FAIL_("TEST_ASSERT_NONNULL(%s) was NULL", #expr); \
+ } while (0)
+
+#define TEST_ASSERT_EQ(a, b) \
+ do { \
+ long long _a = (long long)(a); \
+ long long _b = (long long)(b); \
+ if (_a != _b) \
+ TEST_FAIL_("TEST_ASSERT_EQ(%s, %s): %lld != %lld", \
+ #a, #b, _a, _b); \
+ } while (0)
+
+#define TEST_ASSERT_LT(a, b) \
+ do { \
+ long long _a = (long long)(a); \
+ long long _b = (long long)(b); \
+ if (_a >= _b) \
+ TEST_FAIL_("TEST_ASSERT_LT(%s, %s): %lld >= %lld", \
+ #a, #b, _a, _b); \
+ } while (0)
+
+#define TEST_ASSERT_LE(a, b) \
+ do { \
+ long long _a = (long long)(a); \
+ long long _b = (long long)(b); \
+ if (_a > _b) \
+ TEST_FAIL_("TEST_ASSERT_LE(%s, %s): %lld > %lld", \
+ #a, #b, _a, _b); \
+ } while (0)
+
+#define TEST_ASSERT_GT(a, b) \
+ do { \
+ long long _a = (long long)(a); \
+ long long _b = (long long)(b); \
+ if (_a <= _b) \
+ TEST_FAIL_("TEST_ASSERT_GT(%s, %s): %lld <= %lld", \
+ #a, #b, _a, _b); \
+ } while (0)
+
+#define TEST_ASSERT_GE(a, b) \
+ do { \
+ long long _a = (long long)(a); \
+ long long _b = (long long)(b); \
+ if (_a < _b) \
+ TEST_FAIL_("TEST_ASSERT_GE(%s, %s): %lld < %lld", \
+ #a, #b, _a, _b); \
+ } while (0)
+
+#define TEST_ASSERT_NE(a, b) \
+ do { \
+ long long _a = (long long)(a); \
+ long long _b = (long long)(b); \
+ if (_a == _b) \
+ TEST_FAIL_("TEST_ASSERT_NE(%s, %s): both %lld", \
+ #a, #b, _a); \
+ } while (0)
+
+#define TEST_ASSERT_STRCMP_EQ(actual, expected) \
+ do { \
+ const char *_a = (actual); \
+ const char *_e = (expected); \
+ if (_a == NULL || _e == NULL || strcmp(_a, _e) != 0) \
+ TEST_FAIL_("TEST_ASSERT_STRCMP_EQ(%s, %s): [%s] != [%s]", \
+ #actual, #expected, \
+ _a ? _a : "(null)", _e ? _e : "(null)"); \
+ } while (0)
+
+#define TEST_ASSERT_STRCASECMP_EQ(actual, expected) \
+ do { \
+ const char *_a = (actual); \
+ const char *_e = (expected); \
+ if (_a == NULL || _e == NULL || strcasecmp(_a, _e) != 0) \
+ TEST_FAIL_("TEST_ASSERT_STRCASECMP_EQ(%s, %s): [%s] !~ [%s]", \
+ #actual, #expected, \
+ _a ? _a : "(null)", _e ? _e : "(null)"); \
+ } while (0)
+
+#define TEST_ASSERT_STRNCMP_EQ(actual, expected, n) \
+ do { \
+ const char *_a = (actual); \
+ const char *_e = (expected); \
+ size_t _n = (size_t)(n); \
+ if (_a == NULL || _e == NULL || strncmp(_a, _e, _n) != 0) \
+ TEST_FAIL_("TEST_ASSERT_STRNCMP_EQ(%s, %s, %zu): [%s] != [%s]", \
+ #actual, #expected, _n, \
+ _a ? _a : "(null)", _e ? _e : "(null)"); \
+ } while (0)
+
+#define TEST_ASSERT_STRSTR(haystack, needle) \
+ do { \
+ const char *_h = (haystack); \
+ const char *_n = (needle); \
+ if (_h == NULL || _n == NULL || strstr(_h, _n) == NULL) \
+ TEST_FAIL_("TEST_ASSERT_STRSTR(%s, %s): [%s] does not contain [%s]", \
+ #haystack, #needle, \
+ _h ? _h : "(null)", _n ? _n : "(null)"); \
+ } while (0)
+
+#define TEST_ASSERT_EMPTY_STRING(expr) \
+ do { \
+ const char *_s = (expr); \
+ if (_s == NULL) \
+ TEST_FAIL_("TEST_ASSERT_EMPTY_STRING(%s) was NULL", #expr); \
+ if (_s[0] != '\0') \
+ TEST_FAIL_("TEST_ASSERT_EMPTY_STRING(%s) was [%s]", #expr, _s); \
+ } while (0)
+
+#define TEST_RUN(name, fn) \
+ do { \
+ fprintf(stderr, "%s -> %s:", name, #fn); \
+ fn(); \
+ fprintf(stderr, " PASS\n"); \
+ } while (0)
+
+static inline pkgconf_client_t *
+test_client_new(void)
+{
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+ return pkgconf_client_new(NULL, NULL, pers, NULL, NULL);
+}
+
+#endif // TEST_API_H
diff --git a/tests/api/test-audit.c b/tests/api/test-audit.c
new file mode 100644
index 000000000000..12f9f31e0dda
--- /dev/null
+++ b/tests/api/test-audit.c
@@ -0,0 +1,171 @@
+/*
+ * test-audit.c
+ * Tests for the public libpkgconf audit API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include "test-api.h"
+
+/*
+ * Read the entire contents of f (from the start) into buf.
+ * buf must be large enough; we keep test inputs small.
+ */
+static void
+slurp(FILE *f, char *buf, size_t bufsz)
+{
+ rewind(f);
+ size_t n = fread(buf, 1, bufsz - 1, f);
+ buf[n] = '\0';
+}
+
+static void
+test_audit_log_no_logfile_is_noop(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ // With no audit log set, logging must be a silent no-op and must not crash.
+ pkgconf_audit_log(client, "should go nowhere\n");
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_audit_set_log_and_write(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ FILE *logf = tmpfile();
+ TEST_ASSERT_NONNULL(logf);
+
+ pkgconf_audit_set_log(client, logf);
+ pkgconf_audit_log(client, "hello %s\n", "world");
+
+ char buf[256];
+ slurp(logf, buf, sizeof(buf));
+ TEST_ASSERT_STRCMP_EQ(buf, "hello world\n");
+
+ fclose(logf);
+ pkgconf_client_free(client);
+}
+
+static void
+test_audit_log_multiple_writes(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ FILE *logf = tmpfile();
+ TEST_ASSERT_NONNULL(logf);
+
+ pkgconf_audit_set_log(client, logf);
+ pkgconf_audit_log(client, "first\n");
+ pkgconf_audit_log(client, "second %d\n", 2);
+
+ char buf[256];
+ slurp(logf, buf, sizeof(buf));
+ TEST_ASSERT_STRCMP_EQ(buf, "first\nsecond 2\n");
+
+ fclose(logf);
+ pkgconf_client_free(client);
+}
+
+static void
+test_audit_log_dependency_versionless(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ FILE *logf = tmpfile();
+ TEST_ASSERT_NONNULL(logf);
+
+ pkgconf_audit_set_log(client, logf);
+
+ // A minimal package: only the fields the logger reads
+ pkgconf_pkg_t pkg = { 0 };
+ pkg.id = (char *) "foo";
+ pkg.version = (char *) "1.2.3";
+
+ /* A dependency with PKGCONF_CMP_ANY and no version: the logger
+ * should emit only "id [version]", skipping the comparator */
+ pkgconf_dependency_t dep = { 0 };
+ dep.compare = PKGCONF_CMP_ANY;
+ dep.version = NULL;
+
+ pkgconf_audit_log_dependency(client, &pkg, &dep);
+
+ char buf[256];
+ slurp(logf, buf, sizeof(buf));
+ TEST_ASSERT_STRCMP_EQ(buf, "foo [1.2.3]\n");
+
+ fclose(logf);
+ pkgconf_client_free(client);
+}
+
+static void
+test_audit_log_dependency_versioned(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ FILE *logf = tmpfile();
+ TEST_ASSERT_NONNULL(logf);
+
+ pkgconf_audit_set_log(client, logf);
+
+ pkgconf_pkg_t pkg = { 0 };
+ pkg.id = (char *) "bar";
+ pkg.version = (char *) "2.0";
+
+ /* version set AND compare != ANY: the logger emits the
+ * comparator and required version between id and [version] */
+ pkgconf_dependency_t dep = { 0 };
+ dep.compare = PKGCONF_CMP_GREATER_THAN_EQUAL;
+ dep.version = (char *) "1.5";
+
+ pkgconf_audit_log_dependency(client, &pkg, &dep);
+
+ char buf[256];
+ slurp(logf, buf, sizeof(buf));
+ TEST_ASSERT_STRCMP_EQ(buf, "bar >= 1.5 [2.0]\n");
+
+ fclose(logf);
+ pkgconf_client_free(client);
+}
+
+static void
+test_audit_log_dependency_no_logfile_is_noop(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_pkg_t pkg = { 0 };
+ pkg.id = (char *) "foo";
+ pkg.version = (char *) "1.0";
+
+ pkgconf_dependency_t dep = { 0 };
+ dep.compare = PKGCONF_CMP_ANY;
+
+ // No log set: must be a silent no-op
+ pkgconf_audit_log_dependency(client, &pkg, &dep);
+
+ pkgconf_client_free(client);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_audit_log_no_logfile_is_noop);
+ TEST_RUN(basename, test_audit_set_log_and_write);
+ TEST_RUN(basename, test_audit_log_multiple_writes);
+ TEST_RUN(basename, test_audit_log_dependency_versionless);
+ TEST_RUN(basename, test_audit_log_dependency_versioned);
+ TEST_RUN(basename, test_audit_log_dependency_no_logfile_is_noop);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-buffer.c b/tests/api/test-buffer.c
new file mode 100644
index 000000000000..c17818ba3b4b
--- /dev/null
+++ b/tests/api/test-buffer.c
@@ -0,0 +1,417 @@
+/*
+ * test-buffer.c
+ * Tests for libpkgconf buffer API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "test-api.h"
+
+static void
+test_buffer_empty(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0);
+ TEST_ASSERT_NULL(pkgconf_buffer_str(&buf));
+ TEST_ASSERT_EMPTY_STRING(pkgconf_buffer_str_or_empty(&buf));
+ TEST_ASSERT_EQ(pkgconf_buffer_lastc(&buf), '\0');
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_append(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append(&buf, "hello"));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 5);
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append(&buf, " world"));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello world");
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 11);
+
+ TEST_ASSERT_EQ(pkgconf_buffer_lastc(&buf), 'd');
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_append_slice(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append_slice(&buf, "abcdefgh", 3));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "abc");
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append_slice(&buf, "xyz", 0));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "abc");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_append_fmt(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append_fmt(&buf, "%s=%d", "x", 42));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "x=42");
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append_fmt(&buf, " %s", "ok"));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "x=42 ok");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_prepend(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&buf, "world");
+ TEST_ASSERT_TRUE(pkgconf_buffer_prepend(&buf, "hello "));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello world");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_push_byte(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_push_byte(&buf, 'a'));
+ TEST_ASSERT_TRUE(pkgconf_buffer_push_byte(&buf, 'b'));
+ TEST_ASSERT_TRUE(pkgconf_buffer_push_byte(&buf, 'c'));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "abc");
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 3);
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_trim_byte(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&buf, "hello\n");
+ TEST_ASSERT_TRUE(pkgconf_buffer_trim_byte(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
+
+ pkgconf_buffer_reset(&buf);
+ pkgconf_buffer_push_byte(&buf, 'x');
+ TEST_ASSERT_TRUE(pkgconf_buffer_trim_byte(&buf));
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0);
+ TEST_ASSERT_FALSE(pkgconf_buffer_trim_byte(&buf));
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_reset(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&buf, "some content");
+ TEST_ASSERT_NE(pkgconf_buffer_len(&buf), 0);
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0);
+ TEST_ASSERT_NULL(pkgconf_buffer_str(&buf));
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_append(&buf, "fresh"));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "fresh");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_freeze(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&buf, "frozen");
+ char *out = pkgconf_buffer_freeze(&buf);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "frozen");
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&buf), 0);
+
+ TEST_ASSERT_NULL(pkgconf_buffer_freeze(&buf));
+
+ free(out);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_copy(void)
+{
+ pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&src, "original");
+ pkgconf_buffer_append(&dst, "to be replaced");
+ TEST_ASSERT_TRUE(pkgconf_buffer_copy(&src, &dst));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst), "original");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&src), "original"); // src is unchanged
+
+ pkgconf_buffer_finalize(&src);
+ pkgconf_buffer_finalize(&dst);
+}
+
+static void
+test_buffer_join(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_join(&buf, '/', "usr", "local", "lib", NULL));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "usr/local/lib");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_contains(void)
+{
+ pkgconf_buffer_t hay = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t needle = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&hay, "the quick brown fox");
+
+ pkgconf_buffer_append(&needle, "quick");
+ TEST_ASSERT_TRUE(pkgconf_buffer_contains(&hay, &needle));
+
+ pkgconf_buffer_reset(&needle);
+ pkgconf_buffer_append(&needle, "slow");
+ TEST_ASSERT_FALSE(pkgconf_buffer_contains(&hay, &needle));
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_contains_byte(&hay, 'q'));
+ TEST_ASSERT_FALSE(pkgconf_buffer_contains_byte(&hay, 'z'));
+
+ pkgconf_buffer_finalize(&hay);
+ pkgconf_buffer_finalize(&needle);
+}
+
+static void
+test_buffer_match(void)
+{
+ pkgconf_buffer_t a = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t b = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&a, "identical");
+ pkgconf_buffer_append(&b, "identical");
+ TEST_ASSERT_TRUE(pkgconf_buffer_match(&a, &b));
+
+ // Identical length, but different contents
+ pkgconf_buffer_reset(&b);
+ pkgconf_buffer_append(&b, "different");
+ TEST_ASSERT_FALSE(pkgconf_buffer_match(&a, &b));
+
+ // Different length and contents
+ pkgconf_buffer_reset(&b);
+ pkgconf_buffer_append(&b, "different!");
+ TEST_ASSERT_FALSE(pkgconf_buffer_match(&a, &b));
+
+ pkgconf_buffer_finalize(&a);
+ pkgconf_buffer_finalize(&b);
+}
+
+static void
+test_buffer_has_prefix(void)
+{
+ pkgconf_buffer_t hay = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t pre = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&hay, "/usr/local/lib");
+
+ pkgconf_buffer_append(&pre, "/usr");
+ TEST_ASSERT_TRUE(pkgconf_buffer_has_prefix(&hay, &pre));
+
+ pkgconf_buffer_reset(&pre);
+ pkgconf_buffer_append(&pre, "/opt");
+ TEST_ASSERT_FALSE(pkgconf_buffer_has_prefix(&hay, &pre));
+
+ pkgconf_buffer_finalize(&hay);
+ pkgconf_buffer_finalize(&pre);
+}
+
+static void
+test_buffer_subst(void)
+{
+ pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&src, "prefix=${PREFIX}/share");
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst, &src, "${PREFIX}", "/opt/foo"));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst), "prefix=/opt/foo/share");
+
+ pkgconf_buffer_finalize(&src);
+ pkgconf_buffer_finalize(&dst);
+}
+
+static void
+test_buffer_subst_empty_pattern(void)
+{
+ pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst0 = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst1 = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst2 = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst3 = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst4 = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst5 = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&src, "prefix=${PREFIX}/share");
+
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst0, &src, "", "foo"));
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst1, &src, "", ""));
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst2, &src, "", NULL));
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst3, &src, NULL, "foo"));
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst4, &src, NULL, ""));
+ TEST_ASSERT_TRUE(pkgconf_buffer_subst(&dst5, &src, NULL, NULL));
+
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst0), "prefix=${PREFIX}/share");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst1), "prefix=${PREFIX}/share");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst2), "prefix=${PREFIX}/share");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst3), "prefix=${PREFIX}/share");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst4), "prefix=${PREFIX}/share");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst5), "prefix=${PREFIX}/share");
+
+ pkgconf_buffer_finalize(&src);
+ pkgconf_buffer_finalize(&dst0);
+ pkgconf_buffer_finalize(&dst1);
+ pkgconf_buffer_finalize(&dst2);
+ pkgconf_buffer_finalize(&dst3);
+ pkgconf_buffer_finalize(&dst4);
+ pkgconf_buffer_finalize(&dst5);
+}
+
+static void
+test_buffer_escape(void)
+{
+ pkgconf_buffer_t src = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t dst = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_span_t spans[] = {
+ { ' ', ' ' },
+ { '\t', '\t' },
+ };
+
+ pkgconf_buffer_append(&src, "a b\tc");
+ TEST_ASSERT_TRUE(pkgconf_buffer_escape(&dst, &src, spans, PKGCONF_ARRAY_SIZE(spans)));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&dst), "a\\ b\\\tc");
+
+ pkgconf_buffer_finalize(&src);
+ pkgconf_buffer_finalize(&dst);
+}
+
+static void
+test_str_eq_slice(void)
+{
+ TEST_ASSERT_TRUE(pkgconf_str_eq_slice("hello", "hello", 5));
+ TEST_ASSERT_FALSE(pkgconf_str_eq_slice("hello!", "hello", 5));
+ TEST_ASSERT_FALSE(pkgconf_str_eq_slice("hello", "world", 5));
+ TEST_ASSERT_FALSE(pkgconf_str_eq_slice(NULL, "hello", 5));
+}
+
+static void
+test_span_contains(void)
+{
+ pkgconf_span_t spans[] = {
+ { '0', '9' },
+ { 'a', 'z' },
+ };
+
+ TEST_ASSERT_TRUE(pkgconf_span_contains('5', spans, PKGCONF_ARRAY_SIZE(spans)));
+ TEST_ASSERT_TRUE(pkgconf_span_contains('m', spans, PKGCONF_ARRAY_SIZE(spans)));
+ TEST_ASSERT_FALSE(pkgconf_span_contains('A', spans, PKGCONF_ARRAY_SIZE(spans)));
+ TEST_ASSERT_FALSE(pkgconf_span_contains('!', spans, PKGCONF_ARRAY_SIZE(spans)));
+}
+
+static void
+test_buffer_fputs_nonempty(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = tmpfile();
+ TEST_ASSERT_NONNULL(f);
+
+ pkgconf_buffer_append(&buf, "hello world");
+ TEST_ASSERT_TRUE(pkgconf_buffer_fputs(&buf, f));
+
+ char out[64];
+ rewind(f);
+ size_t n = fread(out, 1, sizeof(out) - 1, f);
+ out[n] = '\0';
+
+ // Buffer contents followed by a newline.
+ TEST_ASSERT_STRCMP_EQ(out, "hello world\n");
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_buffer_fputs_empty(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = tmpfile();
+ TEST_ASSERT_NONNULL(f);
+
+ // An empty buffer writes only a newline.
+ TEST_ASSERT_TRUE(pkgconf_buffer_fputs(&buf, f));
+
+ char out[64];
+ rewind(f);
+ size_t n = fread(out, 1, sizeof(out) - 1, f);
+ out[n] = '\0';
+
+ TEST_ASSERT_STRCMP_EQ(out, "\n");
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+int
+main(int argc, const char **argv)
+{
+ (void) argc;
+
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_buffer_empty);
+ TEST_RUN(basename, test_buffer_append);
+ TEST_RUN(basename, test_buffer_append_slice);
+ TEST_RUN(basename, test_buffer_append_fmt);
+ TEST_RUN(basename, test_buffer_prepend);
+ TEST_RUN(basename, test_buffer_push_byte);
+ TEST_RUN(basename, test_buffer_trim_byte);
+ TEST_RUN(basename, test_buffer_reset);
+ TEST_RUN(basename, test_buffer_freeze);
+ TEST_RUN(basename, test_buffer_copy);
+ TEST_RUN(basename, test_buffer_join);
+ TEST_RUN(basename, test_buffer_contains);
+ TEST_RUN(basename, test_buffer_match);
+ TEST_RUN(basename, test_buffer_has_prefix);
+ TEST_RUN(basename, test_buffer_subst);
+ TEST_RUN(basename, test_buffer_subst_empty_pattern);
+ TEST_RUN(basename, test_buffer_escape);
+ TEST_RUN(basename, test_str_eq_slice);
+ TEST_RUN(basename, test_span_contains);
+ TEST_RUN(basename, test_buffer_fputs_nonempty);
+ TEST_RUN(basename, test_buffer_fputs_empty);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-bytecode.c b/tests/api/test-bytecode.c
new file mode 100644
index 000000000000..7be48941eed9
--- /dev/null
+++ b/tests/api/test-bytecode.c
@@ -0,0 +1,643 @@
+/*
+ * test-bytecode.c
+ * Tests for the public libpkgconf bytecode API:
+ * pkgconf_bytecode_compile and pkgconf_bytecode_eval_str.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "test-api.h"
+
+/*
+ * Build a variable list with the given key/value pairs. Caller frees
+ * with pkgconf_variable_list_free().
+ *
+ * The key/value pairs are stored by compiling `value` as bytecode and
+ * stashing the result on a pkgconf_variable_t inside the list, which
+ * is how the parser builds variable scopes for real .pc files.
+ */
+static void
+seed_variable(pkgconf_list_t *vars, const char *key, const char *value)
+{
+ pkgconf_variable_t *v = pkgconf_variable_get_or_create(vars, key);
+ TEST_ASSERT_NONNULL(v);
+
+ pkgconf_buffer_reset(&v->bcbuf);
+ pkgconf_bytecode_compile(&v->bcbuf, value);
+ pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+}
+
+static void
+test_emit_text_and_eval(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_t bc;
+ bool saw_sysroot = false;
+
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "plain", 5));
+ pkgconf_bytecode_from_buffer(&bc, &bcbuf);
+
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "plain");
+
+ pkgconf_buffer_finalize(&out);
+ pkgconf_buffer_finalize(&bcbuf);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_emit_guards(void)
+{
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, NULL, 5));
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "x", 0));
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, NULL, 3));
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, "x", 0));
+
+ TEST_ASSERT_EQ(pkgconf_buffer_len(&bcbuf), 0);
+
+ pkgconf_buffer_finalize(&bcbuf);
+}
+
+static void
+test_emit_var_and_eval(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_t bc;
+ bool saw_sysroot = false;
+
+ seed_variable(&vars, "name", "world");
+
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "hello ", 6));
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, "name", 4));
+ pkgconf_bytecode_from_buffer(&bc, &bcbuf);
+
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "hello world");
+
+ pkgconf_buffer_finalize(&out);
+ pkgconf_buffer_finalize(&bcbuf);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_emit_sysroot_and_eval(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_t bc;
+ bool saw_sysroot = false;
+
+ pkgconf_client_set_sysroot_dir(client, "/sysroot");
+
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_sysroot(&bcbuf));
+ TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "/usr/include", 12));
+ pkgconf_bytecode_from_buffer(&bc, &bcbuf);
+
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
+ TEST_ASSERT_TRUE(saw_sysroot);
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "/sysroot/usr/include");
+
+ pkgconf_buffer_finalize(&out);
+ pkgconf_buffer_finalize(&bcbuf);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_null_args(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_t bc = { NULL, 0 };
+
+ // NULL client, bc, or out all return false.
+ TEST_ASSERT_FALSE(pkgconf_bytecode_eval(NULL, &vars, &bc, &out, NULL));
+ TEST_ASSERT_FALSE(pkgconf_bytecode_eval(client, &vars, NULL, &out, NULL));
+ TEST_ASSERT_FALSE(pkgconf_bytecode_eval(client, &vars, &bc, NULL, NULL));
+
+ pkgconf_buffer_finalize(&out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_compile_null_args(void)
+{
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_FALSE(pkgconf_bytecode_compile(NULL, "x"));
+ TEST_ASSERT_FALSE(pkgconf_bytecode_compile(&out, NULL));
+
+ pkgconf_buffer_finalize(&out);
+}
+
+static void
+test_dollar_escape(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // $$ collapses to a literal $; the following text is NOT treated as a variable reference
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "price: $$5 and $${notavar}", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "price: $5 and ${notavar}");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_malformed_unclosed(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // A ${ with no closing } is emitted as literal text
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "broken ${unclosed", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "broken ${unclosed");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_empty_braces(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // ${} has a zero-length name; it's emitted as literal text rather than treated as a variable
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "empty ${} here", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "empty ${} here");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_compile_time_sysroot(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ pkgconf_client_set_sysroot_dir(client, "/sysroot");
+
+ // ${pc_sysrootdir} is special-cased at compile time into an OP_SYSROOT op rather than a variable lookup
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_TRUE(saw_sysroot);
+ TEST_ASSERT_STRCMP_EQ(out, "/sysroot/usr/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_sysroot_dot_disables(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // A sysroot of "." disables sysroot rewriting: ${pc_sysrootdir} expands to empty
+ pkgconf_client_set_sysroot_dir(client, ".");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/usr/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_sysroot_root_disables(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // A sysroot of "/" (the root dir) disables rewriting.
+ pkgconf_client_set_sysroot_dir(client, "/");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/usr/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_sysroot_trailing_slash_trimmed(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // Trailing slashes on the sysroot are normalized away, so the result doesn't get a doubled slash
+ pkgconf_client_set_sysroot_dir(client, "/sysroot///");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_TRUE(saw_sysroot);
+ TEST_ASSERT_STRCMP_EQ(out, "/sysroot/usr/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_sysroot_normalizes_to_root_disables(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ /* A sysroot of "//" survives the bare-"/" early check but normalizes down to "/" after
+ * trailing-slash trimming, which then disables rewriting (empty sysroot) */
+ pkgconf_client_set_sysroot_dir(client, "//");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/usr/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_circular_reference(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // A variable that references itself must not infinite-loop: the `expanding` guard breaks the cycle
+ seed_variable(&vars, "loop", "${loop}");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${loop}", &saw_sysroot);
+
+ /* The self-reference is broken; behavior is "expands to nothing further"
+ * We mostly care that it returns rather than hanging or crashing */
+ if (out != NULL)
+ free(out);
+
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_references_var(void)
+{
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_bytecode_compile(&bcbuf, "-I${includedir} -L${libdir}");
+
+ TEST_ASSERT_TRUE(pkgconf_bytecode_references_var(&bcbuf, "includedir"));
+ TEST_ASSERT_TRUE(pkgconf_bytecode_references_var(&bcbuf, "libdir"));
+ TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, "prefix"));
+
+ TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(NULL, "x"));
+ TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, NULL));
+
+ pkgconf_buffer_finalize(&bcbuf);
+}
+
+static void
+test_references_var_text_only(void)
+{
+ pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_bytecode_compile(&bcbuf, "just plain text");
+
+ TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, "anything"));
+
+ pkgconf_buffer_finalize(&bcbuf);
+}
+
+static void
+test_rewrite_selfrefs(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ pkgconf_buffer_t prev = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_compile(&prev, "old");
+
+ pkgconf_buffer_t rhs = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_compile(&rhs, "${foo} new");
+
+ pkgconf_buffer_t rewritten = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_rewrite_selfrefs(&rewritten, &rhs, "foo", &prev));
+
+ pkgconf_bytecode_t bc;
+ pkgconf_bytecode_from_buffer(&bc, &rewritten);
+
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "old new");
+
+ pkgconf_buffer_finalize(&out);
+ pkgconf_buffer_finalize(&rewritten);
+ pkgconf_buffer_finalize(&rhs);
+ pkgconf_buffer_finalize(&prev);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_rewrite_selfrefs_no_match(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ pkgconf_buffer_t prev = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_compile(&prev, "PREV");
+
+ pkgconf_buffer_t rhs = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_compile(&rhs, "${other} tail");
+
+ pkgconf_buffer_t rewritten = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_rewrite_selfrefs(&rewritten, &rhs, "foo", &prev));
+
+ // ${other} survives; seed it so eval can resolve it
+ seed_variable(&vars, "other", "OTHER");
+
+ pkgconf_bytecode_t bc;
+ pkgconf_bytecode_from_buffer(&bc, &rewritten);
+
+ pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+ TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "OTHER tail");
+
+ pkgconf_buffer_finalize(&out);
+ pkgconf_buffer_finalize(&rewritten);
+ pkgconf_buffer_finalize(&rhs);
+ pkgconf_buffer_finalize(&prev);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_plain_text(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // A string with no variable references should round-trip unchanged.
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "plain text value", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "plain text value");
+ TEST_ASSERT_FALSE(saw_sysroot);
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_variable_substitution(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ seed_variable(&vars, "prefix", "/opt/foo");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${prefix}/lib", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/opt/foo/lib");
+ TEST_ASSERT_FALSE(saw_sysroot);
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_nested_variables(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // Recursive expansion: libdir references prefix.
+ seed_variable(&vars, "prefix", "/usr/local");
+ seed_variable(&vars, "libdir", "${prefix}/lib");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "-L${libdir}", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "-L/usr/local/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_undefined_variable(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // Referencing an undefined variable should produce empty substitution, not failure
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "prefix=${nonexistent}/end", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "prefix=/end");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_multiple_variables(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ seed_variable(&vars, "prefix", "/usr");
+ seed_variable(&vars, "exec_prefix", "/usr/local");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "-I${prefix}/include -L${exec_prefix}/lib", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "-I/usr/include -L/usr/local/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_empty_input(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "", &saw_sysroot);
+
+ // An empty input may evaluate to either NULL or an empty string
+ if (out != NULL)
+ {
+ TEST_ASSERT_STRCMP_EQ(out, "");
+ free(out);
+ }
+
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_eval_sysroot_detection(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ pkgconf_client_set_sysroot_dir(client, "/sysroot");
+
+ seed_variable(&vars, "pc_sysrootdir", "/sysroot");
+ seed_variable(&vars, "includedir", "${pc_sysrootdir}/usr/include");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${includedir}", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_TRUE(saw_sysroot);
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_compile_produces_nonempty_buffer(void)
+{
+ pkgconf_buffer_t bc = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_bytecode_compile(&bc, "hello ${world}");
+
+ // The compiled bytecode buffer should be non-empty
+ TEST_ASSERT_NE(pkgconf_buffer_len(&bc), 0);
+
+ pkgconf_buffer_finalize(&bc);
+}
+
+static void
+test_compile_eval_roundtrip(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // Compile directly, then evaluate via the bc field on a variable
+ seed_variable(&vars, "name", "world");
+
+ pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "greeting");
+ TEST_ASSERT_NONNULL(v);
+ pkgconf_buffer_reset(&v->bcbuf);
+ pkgconf_bytecode_compile(&v->bcbuf, "hello ${name}");
+ pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+
+ char *out = pkgconf_variable_eval_str(client, &vars, v, &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "hello world");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_eval_plain_text);
+ TEST_RUN(basename, test_eval_empty_input);
+ TEST_RUN(basename, test_eval_variable_substitution);
+ TEST_RUN(basename, test_eval_nested_variables);
+ TEST_RUN(basename, test_eval_multiple_variables);
+ TEST_RUN(basename, test_eval_undefined_variable);
+ TEST_RUN(basename, test_eval_sysroot_detection);
+ TEST_RUN(basename, test_eval_null_args);
+
+ TEST_RUN(basename, test_emit_guards);
+ TEST_RUN(basename, test_emit_text_and_eval);
+ TEST_RUN(basename, test_emit_var_and_eval);
+ TEST_RUN(basename, test_emit_sysroot_and_eval);
+
+ TEST_RUN(basename, test_dollar_escape);
+ TEST_RUN(basename, test_malformed_unclosed);
+ TEST_RUN(basename, test_empty_braces);
+
+ TEST_RUN(basename, test_circular_reference);
+ TEST_RUN(basename, test_references_var);
+ TEST_RUN(basename, test_references_var_text_only);
+
+ TEST_RUN(basename, test_compile_time_sysroot);
+ TEST_RUN(basename, test_sysroot_dot_disables);
+ TEST_RUN(basename, test_sysroot_root_disables);
+ TEST_RUN(basename, test_sysroot_trailing_slash_trimmed);
+ TEST_RUN(basename, test_sysroot_normalizes_to_root_disables);
+
+ TEST_RUN(basename, test_rewrite_selfrefs);
+ TEST_RUN(basename, test_rewrite_selfrefs_no_match);
+
+ TEST_RUN(basename, test_compile_eval_roundtrip);
+ TEST_RUN(basename, test_compile_produces_nonempty_buffer);
+ TEST_RUN(basename, test_compile_null_args);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-client.c b/tests/api/test-client.c
new file mode 100644
index 000000000000..ae8889b658c8
--- /dev/null
+++ b/tests/api/test-client.c
@@ -0,0 +1,334 @@
+/*
+ * test-client.c
+ * Tests for the public libpkgconf client API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include "test-api.h"
+
+static void
+test_client_new_and_free(void)
+{
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+ pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, NULL);
+ TEST_ASSERT_NONNULL(client);
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_init_and_deinit_stack(void)
+{
+ /* Caller-allocated client, the path the CLI itself uses
+ * (pkgconf_cli_state_t embeds a pkgconf_client_t by value) */
+ pkgconf_client_t client = { 0 };
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+
+ pkgconf_client_init(&client, NULL, NULL, pers, NULL, NULL);
+ pkgconf_client_deinit(&client);
+}
+
+static void
+test_client_sysroot_dir(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_client_set_sysroot_dir(client, "/tmp/sysroot");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_sysroot_dir(client), "/tmp/sysroot");
+
+ pkgconf_client_set_sysroot_dir(client, "/opt/sysroot");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_sysroot_dir(client), "/opt/sysroot");
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_buildroot_dir(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_client_set_buildroot_dir(client, "/tmp/buildroot");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_buildroot_dir(client), "/tmp/buildroot");
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_flags(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_client_set_flags(client, PKGCONF_PKG_PKGF_NO_CACHE | PKGCONF_PKG_PKGF_SKIP_CONFLICTS);
+
+ unsigned int flags = pkgconf_client_get_flags(client);
+ TEST_ASSERT_NE(flags & PKGCONF_PKG_PKGF_NO_CACHE, 0);
+ TEST_ASSERT_NE(flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS, 0);
+ TEST_ASSERT_EQ(flags & PKGCONF_PKG_PKGF_ENV_ONLY, 0);
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_prefix_varname(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_client_set_prefix_varname(client, "prefix");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_prefix_varname(client), "prefix");
+
+ pkgconf_client_set_prefix_varname(client, "custom_prefix");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_client_get_prefix_varname(client), "custom_prefix");
+
+ pkgconf_client_free(client);
+}
+
+/*
+ * Capture buffer for handler tests. Each handler writes the message
+ * into this buffer so the test can verify it fired with the expected
+ * content. Reset before each test that uses it.
+ */
+static char capture_buf[256];
+
+static bool
+capture_handler(const char *msg, const pkgconf_client_t *client, void *data)
+{
+ (void) client;
+ (void) data;
+
+ strncpy(capture_buf, msg, sizeof(capture_buf) - 1);
+ capture_buf[sizeof(capture_buf) - 1] = '\0';
+ return true;
+}
+
+static void
+capture_reset(void)
+{
+ memset(capture_buf, 0, sizeof(capture_buf));
+}
+
+static void
+test_client_error_handler_fires(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ capture_reset();
+
+ pkgconf_client_set_error_handler(client, capture_handler, NULL);
+
+ TEST_ASSERT_EQ(pkgconf_client_get_error_handler(client), capture_handler);
+
+ pkgconf_error(client, "test error: %d", 42);
+
+ TEST_ASSERT_NE(capture_buf[0], '\0');
+ TEST_ASSERT_STRSTR(capture_buf, "test error: 42");
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_warn_handler_fires(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ capture_reset();
+
+ pkgconf_client_set_warn_handler(client, capture_handler, NULL);
+
+ TEST_ASSERT_EQ(pkgconf_client_get_warn_handler(client), capture_handler);
+
+ pkgconf_warn(client, "test warning: %s", "hello");
+
+ TEST_ASSERT_NE(capture_buf[0], '\0');
+ TEST_ASSERT_STRSTR(capture_buf, "test warning: hello");
+
+ pkgconf_client_free(client);
+}
+
+#ifndef PKGCONF_LITE
+static void
+test_client_trace_handler_fires(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ capture_reset();
+
+ pkgconf_client_set_trace_handler(client, capture_handler, NULL);
+
+ TEST_ASSERT_EQ(pkgconf_client_get_trace_handler(client), capture_handler);
+
+ PKGCONF_TRACE(client, "trace message: %d", 7);
+
+ TEST_ASSERT_NE(capture_buf[0], '\0');
+ TEST_ASSERT_STRSTR(capture_buf, "trace message: 7");
+
+ pkgconf_client_free(client);
+}
+#endif
+
+static void
+unveil_capture_handler(const pkgconf_client_t *client, const char *path, const char *permissions)
+{
+ (void) client;
+ (void) permissions;
+
+ if (path != NULL)
+ {
+ strncpy(capture_buf, path, sizeof(capture_buf) - 1);
+ capture_buf[sizeof(capture_buf) - 1] = '\0';
+ }
+}
+
+static void
+test_client_unveil_handler_installation(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_client_set_unveil_handler(client, unveil_capture_handler);
+ TEST_ASSERT_EQ(pkgconf_client_get_unveil_handler(client), unveil_capture_handler);
+
+ pkgconf_client_free(client);
+}
+
+/*
+ * Custom environ handler that returns canned values for known keys
+ * and NULL otherwise.
+ */
+static const char *
+canned_environ_handler(const pkgconf_client_t *client, const char *key)
+{
+ (void) client;
+
+ if (!strcmp(key, "PKG_TEST_VAR"))
+ return "the_value";
+ if (!strcmp(key, "EMPTY_VAR"))
+ return "";
+ if (!strcmp(key, "PKG_CONFIG_SYSTEM_INCLUDE_PATH"))
+ return "/custom/include";
+ if (!strcmp(key, "PKG_CONFIG_SYSTEM_LIBRARY_PATH"))
+ return "/custom/lib";
+
+ return NULL;
+}
+
+static void
+test_client_getenv_via_handler(void)
+{
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+ pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, canned_environ_handler);
+ TEST_ASSERT_NONNULL(client);
+
+ const char *v = pkgconf_client_getenv(client, "PKG_TEST_VAR");
+ TEST_ASSERT_NONNULL(v);
+ TEST_ASSERT_STRCMP_EQ(v, "the_value");
+
+ v = pkgconf_client_getenv(client, "EMPTY_VAR");
+ TEST_ASSERT_NONNULL(v);
+ TEST_ASSERT_STRCMP_EQ(v, "");
+
+ v = pkgconf_client_getenv(client, "UNDEFINED_VAR");
+ TEST_ASSERT_NULL(v);
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_dir_list_build_smoke(void)
+{
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+ pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, NULL);
+ TEST_ASSERT_NONNULL(client);
+
+ pkgconf_client_dir_list_build(client, pers);
+
+ /* The personality's default dir list comes from PKG_DEFAULT_PATH
+ * (set at compile time). After build, the client should have
+ * SOME directories registered; we don't assert specific paths
+ * since they vary by build configuration. */
+ TEST_ASSERT_NONNULL(client->dir_list.head);
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_init_system_paths_from_environ(void)
+{
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+ pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, canned_environ_handler);
+ TEST_ASSERT_NONNULL(client);
+
+ /* With PKG_CONFIG_SYSTEM_{INCLUDE,LIBRARY}_PATH set, init builds the filter dirs from the
+ * environment instead of copying the personality's defaults. */
+ TEST_ASSERT_TRUE(pkgconf_path_match_list("/custom/include", &client->filter_includedirs));
+ TEST_ASSERT_TRUE(pkgconf_path_match_list("/custom/lib", &client->filter_libdirs));
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_client_preload_from_environ(void)
+{
+ pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
+ pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, NULL);
+ TEST_ASSERT_NONNULL(client);
+
+ /* preload_from_environ reads the named var via getenv directly.
+ * Point it at a dir; preload_path will try to load .pc files from there.
+ * An empty/nonexistent dir is fine; we're exercising the split-and-iterate path, not asserting
+ * loads. */
+ setenv("PKG_TEST_PRELOAD", "/nonexistent/dir", 1);
+
+ pkgconf_client_preload_from_environ(client, "PKG_TEST_PRELOAD");
+
+ unsetenv("PKG_TEST_PRELOAD");
+ pkgconf_client_free(client);
+}
+
+#ifndef PKGCONF_LITE
+static void
+test_client_trace_null_client(void)
+{
+ TEST_ASSERT_FALSE(pkgconf_trace(NULL, "test.c", 42, "func", "msg %d", 42));
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_client_new_and_free);
+ TEST_RUN(basename, test_client_init_and_deinit_stack);
+ TEST_RUN(basename, test_client_init_system_paths_from_environ);
+ TEST_RUN(basename, test_client_preload_from_environ);
+
+ TEST_RUN(basename, test_client_sysroot_dir);
+ TEST_RUN(basename, test_client_buildroot_dir);
+ TEST_RUN(basename, test_client_flags);
+ TEST_RUN(basename, test_client_prefix_varname);
+
+ TEST_RUN(basename, test_client_error_handler_fires);
+ TEST_RUN(basename, test_client_warn_handler_fires);
+#ifndef PKGCONF_LITE
+ TEST_RUN(basename, test_client_trace_handler_fires);
+#endif
+ TEST_RUN(basename, test_client_unveil_handler_installation);
+
+ TEST_RUN(basename, test_client_getenv_via_handler);
+
+ TEST_RUN(basename, test_client_dir_list_build_smoke);
+
+#ifndef PKGCONF_LITE
+ TEST_RUN(basename, test_client_trace_null_client);
+#endif
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-dependency.c b/tests/api/test-dependency.c
new file mode 100644
index 000000000000..edd00b620f18
--- /dev/null
+++ b/tests/api/test-dependency.c
@@ -0,0 +1,551 @@
+/*
+ * test-dependency.c
+ * Tests for the public libpkgconf dependency API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include "test-api.h"
+
+static size_t
+dependency_count(const pkgconf_list_t *list)
+{
+ size_t n = 0;
+ const pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+ {
+ n++;
+ }
+
+ return n;
+}
+
+static const pkgconf_dependency_t *
+dependency_at(const pkgconf_list_t *list, size_t index)
+{
+ const pkgconf_node_t *iter;
+ size_t i = 0;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+ {
+ if (i++ == index)
+ return iter->data;
+ }
+
+ return NULL;
+}
+
+static void
+test_comparator_lookup_known(void)
+{
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("="), PKGCONF_CMP_EQUAL);
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("!="), PKGCONF_CMP_NOT_EQUAL);
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("<"), PKGCONF_CMP_LESS_THAN);
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("<="), PKGCONF_CMP_LESS_THAN_EQUAL);
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name(">"), PKGCONF_CMP_GREATER_THAN);
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name(">="), PKGCONF_CMP_GREATER_THAN_EQUAL);
+}
+
+static void
+test_comparator_lookup_unknown(void)
+{
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name("~~"), PKGCONF_CMP_ANY);
+ TEST_ASSERT_EQ(pkgconf_pkg_comparator_lookup_by_name(""), PKGCONF_CMP_ANY);
+}
+
+static void
+test_comparator_roundtrip(void)
+{
+ pkgconf_pkg_comparator_t values[] =
+ {
+ PKGCONF_CMP_NOT_EQUAL,
+ PKGCONF_CMP_ANY,
+ PKGCONF_CMP_LESS_THAN,
+ PKGCONF_CMP_LESS_THAN_EQUAL,
+ PKGCONF_CMP_EQUAL,
+ PKGCONF_CMP_GREATER_THAN,
+ PKGCONF_CMP_GREATER_THAN_EQUAL,
+ };
+
+ for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(values); i++)
+ {
+ pkgconf_dependency_t dep = { 0 };
+ dep.compare = values[i];
+
+ const char *str = pkgconf_pkg_get_comparator(&dep);
+ TEST_ASSERT_NONNULL(str);
+
+ // ANY's value does not round-trip so do not check
+ if (values[i] == PKGCONF_CMP_ANY)
+ continue;
+
+ pkgconf_pkg_comparator_t back = pkgconf_pkg_comparator_lookup_by_name(str);
+ TEST_ASSERT_EQ(back, values[i]);
+ }
+}
+
+static void
+test_parse_str_single(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "foo", 0);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+ const pkgconf_dependency_t *d = dependency_at(&deps, 0);
+ TEST_ASSERT_NONNULL(d);
+ TEST_ASSERT_STRCMP_EQ(d->package, "foo");
+ TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_ANY);
+ TEST_ASSERT_NULL(d->version);
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_parse_str_versioned(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "foo >= 1.2.3", 0);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+ const pkgconf_dependency_t *d = dependency_at(&deps, 0);
+ TEST_ASSERT_NONNULL(d);
+ TEST_ASSERT_STRCMP_EQ(d->package, "foo");
+ TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_GREATER_THAN_EQUAL);
+ TEST_ASSERT_STRCMP_EQ(d->version, "1.2.3");
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_parse_str_multiple_space_separated(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "foo bar baz", 0);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 3);
+
+ const pkgconf_dependency_t *d0 = dependency_at(&deps, 0);
+ const pkgconf_dependency_t *d1 = dependency_at(&deps, 1);
+ const pkgconf_dependency_t *d2 = dependency_at(&deps, 2);
+ TEST_ASSERT_NONNULL(d0);
+ TEST_ASSERT_NONNULL(d1);
+ TEST_ASSERT_NONNULL(d2);
+ TEST_ASSERT_STRCMP_EQ(d0->package, "foo");
+ TEST_ASSERT_STRCMP_EQ(d1->package, "bar");
+ TEST_ASSERT_STRCMP_EQ(d2->package, "baz");
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_parse_str_multiple_comma_separated(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "foo, bar, baz", 0);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 3);
+
+ const pkgconf_dependency_t *d0 = dependency_at(&deps, 0);
+ const pkgconf_dependency_t *d1 = dependency_at(&deps, 1);
+ const pkgconf_dependency_t *d2 = dependency_at(&deps, 2);
+ TEST_ASSERT_NONNULL(d0);
+ TEST_ASSERT_NONNULL(d1);
+ TEST_ASSERT_NONNULL(d2);
+ TEST_ASSERT_STRCMP_EQ(d0->package, "foo");
+ TEST_ASSERT_STRCMP_EQ(d1->package, "bar");
+ TEST_ASSERT_STRCMP_EQ(d2->package, "baz");
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_parse_str_mixed_versioned_and_bare(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "foo >= 1.0, bar, baz < 2.0", 0);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 3);
+
+ const pkgconf_dependency_t *d0 = dependency_at(&deps, 0);
+ const pkgconf_dependency_t *d1 = dependency_at(&deps, 1);
+ const pkgconf_dependency_t *d2 = dependency_at(&deps, 2);
+
+ TEST_ASSERT_NONNULL(d0);
+ TEST_ASSERT_NONNULL(d1);
+ TEST_ASSERT_NONNULL(d2);
+
+ TEST_ASSERT_STRCMP_EQ(d0->package, "foo");
+ TEST_ASSERT_EQ(d0->compare, PKGCONF_CMP_GREATER_THAN_EQUAL);
+ TEST_ASSERT_STRCMP_EQ(d0->version, "1.0");
+
+ TEST_ASSERT_STRCMP_EQ(d1->package, "bar");
+ TEST_ASSERT_EQ(d1->compare, PKGCONF_CMP_ANY);
+
+ TEST_ASSERT_STRCMP_EQ(d2->package, "baz");
+ TEST_ASSERT_EQ(d2->compare, PKGCONF_CMP_LESS_THAN);
+ TEST_ASSERT_STRCMP_EQ(d2->version, "2.0");
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_parse_str_empty(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "", 0);
+ TEST_ASSERT_EQ(dependency_count(&deps), 0);
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_parse_str_all_comparators(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_parse_str(client, &deps, "a = 1, b != 2, c < 3, d <= 4, e > 5, f >= 6", 0);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 6);
+
+ const pkgconf_dependency_t *d0 = dependency_at(&deps, 0);
+ const pkgconf_dependency_t *d1 = dependency_at(&deps, 1);
+ const pkgconf_dependency_t *d2 = dependency_at(&deps, 2);
+ const pkgconf_dependency_t *d3 = dependency_at(&deps, 3);
+ const pkgconf_dependency_t *d4 = dependency_at(&deps, 4);
+ const pkgconf_dependency_t *d5 = dependency_at(&deps, 5);
+
+ TEST_ASSERT_NONNULL(d0);
+ TEST_ASSERT_NONNULL(d1);
+ TEST_ASSERT_NONNULL(d2);
+ TEST_ASSERT_NONNULL(d3);
+ TEST_ASSERT_NONNULL(d4);
+ TEST_ASSERT_NONNULL(d5);
+
+ TEST_ASSERT_EQ(d0->compare, PKGCONF_CMP_EQUAL);
+ TEST_ASSERT_EQ(d1->compare, PKGCONF_CMP_NOT_EQUAL);
+ TEST_ASSERT_EQ(d2->compare, PKGCONF_CMP_LESS_THAN);
+ TEST_ASSERT_EQ(d3->compare, PKGCONF_CMP_LESS_THAN_EQUAL);
+ TEST_ASSERT_EQ(d4->compare, PKGCONF_CMP_GREATER_THAN);
+ TEST_ASSERT_EQ(d5->compare, PKGCONF_CMP_GREATER_THAN_EQUAL);
+
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_dependency_add(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_t *d = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_GREATER_THAN_EQUAL, 0);
+ TEST_ASSERT_NONNULL(d);
+ TEST_ASSERT_STRCMP_EQ(d->package, "foo");
+ TEST_ASSERT_STRCMP_EQ(d->version, "1.0");
+ TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_GREATER_THAN_EQUAL);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+
+ pkgconf_dependency_unref(client, d);
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_dependency_add_no_version(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_t *d = pkgconf_dependency_add(client, &deps, "foo", NULL, PKGCONF_CMP_ANY, 0);
+ TEST_ASSERT_NONNULL(d);
+ TEST_ASSERT_STRCMP_EQ(d->package, "foo");
+ TEST_ASSERT_EQ(d->compare, PKGCONF_CMP_ANY);
+
+ pkgconf_dependency_unref(client, d);
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_dependency_add_multiple(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_t *d0 = pkgconf_dependency_add(client, &deps, "foo", NULL, PKGCONF_CMP_ANY, 0);
+ pkgconf_dependency_t *d1 = pkgconf_dependency_add(client, &deps, "bar", "2.0", PKGCONF_CMP_EQUAL, 0);
+ pkgconf_dependency_t *d2 = pkgconf_dependency_add(client, &deps, "baz", "3.0", PKGCONF_CMP_LESS_THAN, 0);
+
+ TEST_ASSERT_NONNULL(d0);
+ TEST_ASSERT_NONNULL(d1);
+ TEST_ASSERT_NONNULL(d2);
+
+ TEST_ASSERT_EQ(dependency_count(&deps), 3);
+
+ pkgconf_dependency_unref(client, d0);
+ pkgconf_dependency_unref(client, d1);
+ pkgconf_dependency_unref(client, d2);
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_dependency_collision_drops_flagged_newcomer(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_t *first = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, 0);
+ TEST_ASSERT_NONNULL(first);
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+
+ /* Adding the same dep WITH flags collides; the flagged newcomer
+ * is dropped in favour of the existing unflagged node, so _add
+ * returns NULL and the count stays at 1 */
+ pkgconf_dependency_t *second = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, PKGCONF_PKG_DEPF_INTERNAL);
+ TEST_ASSERT_NULL(second);
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+
+ pkgconf_dependency_unref(client, first);
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_dependency_collision_drops_flagged_existing(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t deps = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_dependency_t *first = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, PKGCONF_PKG_DEPF_INTERNAL);
+ TEST_ASSERT_NONNULL(first);
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+
+ /* Adding the same dep UNFLAGGED collides; the existing flagged
+ * node is deleted and unref'd, the unflagged newcomer takes its
+ * place; count stays at 1, but the node is now the new one */
+ pkgconf_dependency_t *second = pkgconf_dependency_add(client, &deps, "foo", "1.0", PKGCONF_CMP_EQUAL, 0);
+ TEST_ASSERT_NONNULL(second);
+ TEST_ASSERT_EQ(dependency_count(&deps), 1);
+ TEST_ASSERT_EQ(second->flags, 0);
+
+ pkgconf_dependency_unref(client, first);
+ pkgconf_dependency_unref(client, second);
+ pkgconf_dependency_free(&deps);
+ pkgconf_client_free(client);
+}
+
+static void
+test_version_equal(void)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.0", "1.0"), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.2.3", "1.2.3"), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version("", ""), 0);
+}
+
+static void
+test_version_simple_numeric(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0", "1.1"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.1", "1.0"), 0);
+
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0", "2.0"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("2.0", "1.0"), 0);
+
+ TEST_ASSERT_LT(pkgconf_compare_version("1.2.3", "1.2.4"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.2.3", "1.3.0"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.2.3", "2.0.0"), 0);
+}
+
+static void
+test_version_numeric_segments_not_lexical(void)
+{
+ TEST_ASSERT_GT(pkgconf_compare_version("1.10", "1.9"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.100", "1.99"), 0);
+}
+
+static void
+test_version_different_lengths(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0", "1.0.1"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0.1", "1.0"), 0);
+}
+
+static void
+test_version_alpha_suffix(void)
+{
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0a", "1.0"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0alpha", "1.0"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0rc1", "1.0"), 0);
+
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0alpha", "1.0beta"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0beta", "1.0rc"), 0);
+}
+
+static void
+test_version_tilde_prerelease(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~rc1", "1.0"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~alpha", "1.0"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~beta", "1.0~rc"), 0);
+
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~rc1", "1.0rc1"), 0);
+}
+
+static void
+test_version_numeric_beats_alpha(void)
+{
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0.1", "1.0a"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0a", "1.0.1"), 0);
+}
+
+static void
+test_version_alpha_ordering(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0a", "1.0b"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0b", "1.0a"), 0);
+}
+
+static void
+test_version_dotted_vs_hyphenated(void)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.0-1", "1.0.1"), 0);
+}
+
+static void
+test_version_leading_zeros(void)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.01", "1.1"), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version("01.0", "1.0"), 0);
+}
+
+static void
+test_version_trailing_zero_segments(void)
+{
+ /* Pkgconf does NOT treat "1.0" and "1.0.0" as equivalent.
+ * Even a zero trailing numeric segment is additional content that makes the version greater.
+ */
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0", "1.0.0"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0.0", "1.0"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1", "1.0"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1", "1.0.0.0"), 0);
+}
+
+static void
+test_version_null_handling(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version(NULL, "1.0"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0", NULL), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version(NULL, NULL), 0);
+}
+
+static void
+test_version_tilde_both_sides(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~", "1.0a"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0a", "1.0~"), 0);
+
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.0~rc", "1.0~rc"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~a", "1.0~b"), 0);
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0~1", "1.0~2"), 0);
+}
+
+static void
+test_version_separator_equivalence(void)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.0", "1-0"), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.2.3", "1_2_3"), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version("1..2", "1.2"), 0);
+}
+
+static void
+test_version_case_insensitive(void)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.0RC1", "1.0rc1"), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version("1.0A", "1.0a"), 0);
+}
+
+static void
+test_version_alpha_prefix(void)
+{
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0alpha", "1.0alphabeta"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0alphabeta", "1.0alpha"), 0);
+
+ TEST_ASSERT_LT(pkgconf_compare_version("1.0a", "1.0ab"), 0);
+ TEST_ASSERT_GT(pkgconf_compare_version("1.0ab", "1.0a"), 0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_comparator_lookup_known);
+ TEST_RUN(basename, test_comparator_lookup_unknown);
+ TEST_RUN(basename, test_comparator_roundtrip);
+
+ TEST_RUN(basename, test_parse_str_empty);
+ TEST_RUN(basename, test_parse_str_single);
+ TEST_RUN(basename, test_parse_str_versioned);
+ TEST_RUN(basename, test_parse_str_multiple_space_separated);
+ TEST_RUN(basename, test_parse_str_multiple_comma_separated);
+ TEST_RUN(basename, test_parse_str_mixed_versioned_and_bare);
+ TEST_RUN(basename, test_parse_str_all_comparators);
+
+ TEST_RUN(basename, test_dependency_add);
+ TEST_RUN(basename, test_dependency_add_no_version);
+ TEST_RUN(basename, test_dependency_add_multiple);
+ TEST_RUN(basename, test_dependency_collision_drops_flagged_newcomer);
+ TEST_RUN(basename, test_dependency_collision_drops_flagged_existing);
+
+ TEST_RUN(basename, test_version_equal);
+ TEST_RUN(basename, test_version_simple_numeric);
+ TEST_RUN(basename, test_version_numeric_segments_not_lexical);
+ TEST_RUN(basename, test_version_different_lengths);
+ TEST_RUN(basename, test_version_alpha_suffix);
+ TEST_RUN(basename, test_version_tilde_prerelease);
+ TEST_RUN(basename, test_version_numeric_beats_alpha);
+ TEST_RUN(basename, test_version_alpha_ordering);
+ TEST_RUN(basename, test_version_dotted_vs_hyphenated);
+ TEST_RUN(basename, test_version_leading_zeros);
+ TEST_RUN(basename, test_version_trailing_zero_segments);
+ TEST_RUN(basename, test_version_null_handling);
+ TEST_RUN(basename, test_version_tilde_both_sides);
+ TEST_RUN(basename, test_version_separator_equivalence);
+ TEST_RUN(basename, test_version_case_insensitive);
+ TEST_RUN(basename, test_version_alpha_prefix);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-fileio.c b/tests/api/test-fileio.c
new file mode 100644
index 000000000000..a4d5f81679a6
--- /dev/null
+++ b/tests/api/test-fileio.c
@@ -0,0 +1,236 @@
+/*
+ * test-fileio.c
+ * Tests for the public libpkgconf file i/o API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include "test-api.h"
+
+static FILE *
+fmemstream(const char *contents)
+{
+ FILE *f = tmpfile();
+
+ TEST_ASSERT_NONNULL(f);
+
+ fwrite(contents, 1, strlen(contents), f);
+ rewind(f);
+
+ return f;
+}
+
+static void
+test_fgetline_no_trailing_newline(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("hello");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_empty_stream(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("");
+
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_lf(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("hello\nworld\n");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_crlf(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("hello\r\nworld\r\n");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_lone_cr(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("hello\rworld\r");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_backslash_continuation_lf(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("foo\\\nbar\n");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar");
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_backslash_continuation_crlf(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("foo\\\r\nbar\r\n");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_fgetline_backslash_continuation_lone_cr(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("foo\\\rbar\r");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+// A backslash not immediately followed by a newline is not a continuation,
+// so it must be preserved literally in the output.
+static void
+test_fgetline_backslash_not_continuation(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ FILE *f = fmemstream("foo\\bar\n");
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foo\\bar");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+// fgets() only stops on '\n', a full read buffer, or EOF. NOT on on a lone '\r'.
+// If a '\r' happens to be the very last byte fgets() manages to read
+// before the buffer fills up, the matching '\n' is still unread in the
+// stream and isn't visible to pkgconf_fgetline()'s lookahead, so the CRLF
+// pair gets split across two fgets() calls and is misparsed as two lines.
+static void
+test_fgetline_crlf_split_across_fgets_buffer(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ size_t prefix_len = PKGCONF_ITEM_SIZE - 2;
+ char *content = malloc(prefix_len + strlen("\r\nworld\n") + 1);
+ FILE *f;
+
+ TEST_ASSERT_NONNULL(content);
+ memset(content, 'a', prefix_len);
+ strcpy(content + prefix_len, "\r\nworld\n");
+
+ f = fmemstream(content);
+ free(content);
+
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_EQ(strlen(pkgconf_buffer_str(&buf)), prefix_len);
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
+
+ pkgconf_buffer_reset(&buf);
+ TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
+
+ fclose(f);
+ pkgconf_buffer_finalize(&buf);
+}
+
+int
+main(int argc, const char **argv)
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_fgetline_no_trailing_newline);
+ TEST_RUN(basename, test_fgetline_empty_stream);
+ TEST_RUN(basename, test_fgetline_lf);
+ TEST_RUN(basename, test_fgetline_crlf);
+ TEST_RUN(basename, test_fgetline_lone_cr);
+ TEST_RUN(basename, test_fgetline_backslash_continuation_lf);
+ TEST_RUN(basename, test_fgetline_backslash_continuation_crlf);
+ TEST_RUN(basename, test_fgetline_backslash_continuation_lone_cr);
+ TEST_RUN(basename, test_fgetline_backslash_not_continuation);
+ TEST_RUN(basename, test_fgetline_crlf_split_across_fgets_buffer);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-fragment.c b/tests/api/test-fragment.c
new file mode 100644
index 000000000000..3d49145ae518
--- /dev/null
+++ b/tests/api/test-fragment.c
@@ -0,0 +1,376 @@
+/*
+ * test-fragment.c
+ * Tests for the public libpkgconf fragment API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include "test-api.h"
+
+static size_t
+fragment_count(const pkgconf_list_t *list)
+{
+ size_t n = 0;
+ const pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+ {
+ n++;
+ }
+
+ return n;
+}
+
+static const pkgconf_fragment_t *
+fragment_at(const pkgconf_list_t *list, size_t index)
+{
+ const pkgconf_node_t *iter;
+ size_t i = 0;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+ {
+ if (i++ == index)
+ return iter->data;
+ }
+
+ return NULL;
+}
+
+/*
+ * Render a fragment list to a newly-allocated C string for assertions.
+ * Caller frees.
+ */
+static char *
+render_to_string(const pkgconf_list_t *list)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_fragment_render_buf(list, &buf, false, NULL, ' ');
+
+ if (pkgconf_buffer_str(&buf) == NULL)
+ return strdup("");
+
+ char *out = strdup(pkgconf_buffer_str(&buf));
+ pkgconf_buffer_finalize(&buf);
+ return out;
+}
+
+static void
+test_fragment_parse_cflags(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_fragment_parse(client, &frags, &vars, "-I/usr/include -DFOO=1", 0));
+ TEST_ASSERT_EQ(fragment_count(&frags), 2);
+
+ const pkgconf_fragment_t *first = fragment_at(&frags, 0);
+ TEST_ASSERT_NONNULL(first);
+ TEST_ASSERT_EQ(first->type, 'I');
+ TEST_ASSERT_STRCMP_EQ(first->data, "/usr/include");
+
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_parse_libs(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_fragment_parse(client, &frags, &vars, "-L/usr/lib -lfoo -lbar", 0));
+ TEST_ASSERT_EQ(fragment_count(&frags), 3);
+
+ const pkgconf_fragment_t *f0 = fragment_at(&frags, 0);
+ const pkgconf_fragment_t *f1 = fragment_at(&frags, 1);
+ const pkgconf_fragment_t *f2 = fragment_at(&frags, 2);
+
+ TEST_ASSERT_NONNULL(f0);
+ TEST_ASSERT_NONNULL(f1);
+ TEST_ASSERT_NONNULL(f2);
+
+ TEST_ASSERT_EQ(f0->type, 'L');
+ TEST_ASSERT_STRCMP_EQ(f0->data, "/usr/lib");
+ TEST_ASSERT_EQ(f1->type, 'l');
+ TEST_ASSERT_STRCMP_EQ(f1->data, "foo");
+ TEST_ASSERT_EQ(f2->type, 'l');
+ TEST_ASSERT_STRCMP_EQ(f2->data, "bar");
+
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_parse_empty(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ TEST_ASSERT_TRUE(pkgconf_fragment_parse(client, &frags, &vars, "", 0));
+ TEST_ASSERT_EQ(fragment_count(&frags), 0);
+
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_add_single(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_fragment_add(client, &frags, &vars, "-I/opt/include", 0);
+
+ TEST_ASSERT_EQ(fragment_count(&frags), 1);
+
+ const pkgconf_fragment_t *f = fragment_at(&frags, 0);
+ TEST_ASSERT_NONNULL(f);
+ TEST_ASSERT_EQ(f->type, 'I');
+ TEST_ASSERT_STRCMP_EQ(f->data, "/opt/include");
+
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_render_cflags(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_fragment_parse(client, &frags, &vars, "-I/usr/include -I/opt/include", 0);
+
+ char *rendered = render_to_string(&frags);
+ TEST_ASSERT_NONNULL(rendered);
+ TEST_ASSERT_STRCMP_EQ(rendered, "-I/usr/include -I/opt/include");
+
+ free(rendered);
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_render_libs(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_fragment_parse(client, &frags, &vars, "-L/usr/lib -lfoo", 0);
+
+ char *rendered = render_to_string(&frags);
+ TEST_ASSERT_NONNULL(rendered);
+ TEST_ASSERT_STRCMP_EQ(rendered, "-L/usr/lib -lfoo");
+
+ free(rendered);
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_render_empty(void)
+{
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+
+ char *rendered = render_to_string(&frags);
+ TEST_ASSERT_NONNULL(rendered);
+ TEST_ASSERT_EMPTY_STRING(rendered);
+
+ free(rendered);
+}
+
+// Filter predicate: keep only -I (include) fragments.
+static bool
+filter_only_includes(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data)
+{
+ (void) client;
+ (void) data;
+ return frag->type == 'I';
+}
+
+// Filter predicate: keep only -l (library name) fragments.
+static bool
+filter_only_libnames(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data)
+{
+ (void) client;
+ (void) data;
+ return frag->type == 'l';
+}
+
+// Filter predicate: keep nothing.
+static bool
+filter_nothing(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data)
+{
+ (void) client;
+ (void) frag;
+ (void) data;
+ return false;
+}
+
+static void
+test_fragment_filter_only_includes(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t src = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_fragment_parse(client, &src, &vars, "-I/usr/include -L/usr/lib -lfoo -I/opt/include", 0);
+
+ pkgconf_fragment_filter(client, &dst, &src, filter_only_includes, NULL);
+ TEST_ASSERT_EQ(fragment_count(&dst), 2);
+
+ const pkgconf_fragment_t *f0 = fragment_at(&dst, 0);
+ const pkgconf_fragment_t *f1 = fragment_at(&dst, 1);
+ TEST_ASSERT_NONNULL(f0);
+ TEST_ASSERT_NONNULL(f1);
+ TEST_ASSERT_EQ(f0->type, 'I');
+ TEST_ASSERT_STRCMP_EQ(f0->data, "/usr/include");
+ TEST_ASSERT_EQ(f1->type, 'I');
+ TEST_ASSERT_STRCMP_EQ(f1->data, "/opt/include");
+
+ pkgconf_fragment_free(&dst);
+ pkgconf_fragment_free(&src);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_filter_only_libnames(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t src = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_fragment_parse(client, &src, &vars, "-L/usr/lib -lfoo -lbar -I/usr/include", 0);
+
+ pkgconf_fragment_filter(client, &dst, &src, filter_only_libnames, NULL);
+
+ TEST_ASSERT_EQ(fragment_count(&dst), 2);
+
+ const pkgconf_fragment_t *f0 = fragment_at(&dst, 0);
+ const pkgconf_fragment_t *f1 = fragment_at(&dst, 1);
+ TEST_ASSERT_NONNULL(f0);
+ TEST_ASSERT_NONNULL(f1);
+ TEST_ASSERT_EQ(f0->type, 'l');
+ TEST_ASSERT_STRCMP_EQ(f0->data, "foo");
+ TEST_ASSERT_EQ(f1->type, 'l');
+ TEST_ASSERT_STRCMP_EQ(f1->data, "bar");
+
+ pkgconf_fragment_free(&dst);
+ pkgconf_fragment_free(&src);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_filter_keeps_nothing(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t src = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_fragment_parse(client, &src, &vars, "-I/usr/include -lfoo", 0);
+
+ pkgconf_fragment_filter(client, &dst, &src, filter_nothing, NULL);
+
+ TEST_ASSERT_EQ(fragment_count(&dst), 0);
+
+ pkgconf_fragment_free(&dst);
+ pkgconf_fragment_free(&src);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_has_system_dir_matches(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_path_add("/usr/include", &client->filter_includedirs, false);
+
+ pkgconf_fragment_parse(client, &frags, &vars, "-I/usr/include -I/opt/include", 0);
+
+ const pkgconf_fragment_t *system = fragment_at(&frags, 0);
+ const pkgconf_fragment_t *other = fragment_at(&frags, 1);
+ TEST_ASSERT_NONNULL(system);
+ TEST_ASSERT_NONNULL(other);
+
+ TEST_ASSERT_TRUE(pkgconf_fragment_has_system_dir(client, system));
+ TEST_ASSERT_FALSE(pkgconf_fragment_has_system_dir(client, other));
+
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_fragment_has_system_dir_libs(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_path_add("/usr/lib", &client->filter_libdirs, false);
+
+ pkgconf_fragment_parse(client, &frags, &vars, "-L/usr/lib -L/opt/lib", 0);
+
+ const pkgconf_fragment_t *system = fragment_at(&frags, 0);
+ const pkgconf_fragment_t *other = fragment_at(&frags, 1);
+ TEST_ASSERT_NONNULL(system);
+ TEST_ASSERT_NONNULL(other);
+
+ TEST_ASSERT_TRUE(pkgconf_fragment_has_system_dir(client, system));
+ TEST_ASSERT_FALSE(pkgconf_fragment_has_system_dir(client, other));
+
+ pkgconf_fragment_free(&frags);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_fragment_parse_empty);
+ TEST_RUN(basename, test_fragment_parse_cflags);
+ TEST_RUN(basename, test_fragment_parse_libs);
+ TEST_RUN(basename, test_fragment_add_single);
+ TEST_RUN(basename, test_fragment_render_empty);
+ TEST_RUN(basename, test_fragment_render_cflags);
+ TEST_RUN(basename, test_fragment_render_libs);
+ TEST_RUN(basename, test_fragment_filter_only_includes);
+ TEST_RUN(basename, test_fragment_filter_only_libnames);
+ TEST_RUN(basename, test_fragment_filter_keeps_nothing);
+ TEST_RUN(basename, test_fragment_has_system_dir_matches);
+ TEST_RUN(basename, test_fragment_has_system_dir_libs);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-license.c b/tests/api/test-license.c
new file mode 100644
index 000000000000..d77ed21a4c86
--- /dev/null
+++ b/tests/api/test-license.c
@@ -0,0 +1,311 @@
+/*
+ * test-license.c
+ * Tests for the public libpkgconf license API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include "test-api.h"
+
+static size_t
+license_count(const pkgconf_list_t *list)
+{
+ size_t n = 0;
+ const pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+ {
+ n++;
+ }
+
+ return n;
+}
+
+static char *
+render_to_string(pkgconf_client_t *client, const pkgconf_list_t *list)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_license_render(client, list, &buf);
+
+ char *out = strdup(pkgconf_buffer_str_or_empty(&buf));
+ pkgconf_buffer_finalize(&buf);
+ return out;
+}
+
+static void
+test_license_insert_and_free(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_insert(client, &licenses, PKGCONF_LICENSE_EXPRESSION, "BSD-3-Clause");
+ TEST_ASSERT_EQ(license_count(&licenses), 1);
+
+ const pkgconf_license_t *l = licenses.head->data;
+ TEST_ASSERT_NONNULL(l);
+ TEST_ASSERT_EQ(l->type, PKGCONF_LICENSE_EXPRESSION);
+ TEST_ASSERT_STRCMP_EQ(l->data, "BSD-3-Clause");
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_free_empty(void)
+{
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ // Freeing an empty list is a no-op. Smoke test.
+ pkgconf_license_free(&licenses);
+
+ // Smoke test
+ pkgconf_license_free(NULL);
+}
+
+static void
+test_license_evaluate_single(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "BSD-3-Clause", 0);
+
+ TEST_ASSERT_EQ(license_count(&licenses), 1);
+ const pkgconf_license_t *l = licenses.head->data;
+ TEST_ASSERT_EQ(l->type, PKGCONF_LICENSE_EXPRESSION);
+ TEST_ASSERT_STRCMP_EQ(l->data, "BSD-3-Clause");
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_or(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "MIT OR ISC", 0);
+
+ TEST_ASSERT_EQ(license_count(&licenses), 3);
+
+ char *rendered = render_to_string(client, &licenses);
+ TEST_ASSERT_STRCMP_EQ(rendered, "MIT OR ISC");
+ free(rendered);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_and(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "LGPL-2.1-only AND MIT", 0);
+
+ char *rendered = render_to_string(client, &licenses);
+ TEST_ASSERT_STRCMP_EQ(rendered, "LGPL-2.1-only AND MIT");
+ free(rendered);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_multiple_keys_implicit_and(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "BSD-3-Clause", 0);
+ pkgconf_license_evaluate_str(client, &licenses, "BSD-2-Clause", 0);
+
+ char *rendered = render_to_string(client, &licenses);
+ TEST_ASSERT_STRCMP_EQ(rendered, "BSD-3-Clause AND BSD-2-Clause");
+ free(rendered);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_brackets(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "ISC AND (BSD-3-Clause AND BSD-2-Clause)", 0);
+
+ char *rendered = render_to_string(client, &licenses);
+ TEST_ASSERT_STRCMP_EQ(rendered, "ISC AND (BSD-3-Clause AND BSD-2-Clause)");
+ free(rendered);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_empty(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "", 0);
+ TEST_ASSERT_EQ(license_count(&licenses), 0);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_sanitizes(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ /* The sanitiser strips characters outside the allowed set
+ * (alnum, '-', '+', '(', ')', '.', ':'). A token of pure
+ * junk should sanitise to empty and be skipped. */
+ pkgconf_license_evaluate_str(client, &licenses, "BSD-3-Clause", 0);
+
+ const pkgconf_license_t *l = licenses.head->data;
+ TEST_ASSERT_STRCMP_EQ(l->data, "BSD-3-Clause");
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_render_empty(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ char *rendered = render_to_string(client, &licenses);
+ TEST_ASSERT_EMPTY_STRING(rendered);
+ free(rendered);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_render_single(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_insert(client, &licenses, PKGCONF_LICENSE_EXPRESSION, "MIT");
+
+ char *rendered = render_to_string(client, &licenses);
+ TEST_ASSERT_STRCMP_EQ(rendered, "MIT");
+ free(rendered);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_copy_list(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t src = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &src, "MIT OR ISC", 0);
+ size_t src_count = license_count(&src);
+
+ pkgconf_license_copy_list(client, &dst, &src);
+ TEST_ASSERT_EQ(license_count(&dst), src_count);
+
+ /* The copy is independent: freeing the source must not affect
+ * the destination's rendered output. */
+ pkgconf_license_free(&src);
+
+ char *rendered = render_to_string(client, &dst);
+ TEST_ASSERT_STRCMP_EQ(rendered, "MIT OR ISC");
+ free(rendered);
+
+ pkgconf_license_free(&dst);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_long_sanitized_token(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ char token[512];
+ token[0] = 'A';
+ memset(token + 1, '_', 400);
+ token[401] = '\0';
+
+ pkgconf_license_evaluate_str(client, &licenses, token, 0);
+
+ TEST_ASSERT_EQ(license_count(&licenses), 1);
+ const pkgconf_license_t *l = licenses.head->data;
+ TEST_ASSERT_EQ(l->type, PKGCONF_LICENSE_EXPRESSION);
+ TEST_ASSERT_STRCMP_EQ(l->data, "A");
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+static void
+test_license_evaluate_unterminated_quote(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t licenses = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_license_evaluate_str(client, &licenses, "\"", 0);
+ TEST_ASSERT_EQ(license_count(&licenses), 0);
+
+ pkgconf_license_evaluate_str(client, &licenses, "MIT \"unterminated", 0);
+ TEST_ASSERT_EQ(license_count(&licenses), 0);
+
+ pkgconf_license_evaluate_str(client, &licenses, "\\", 0);
+ TEST_ASSERT_EQ(license_count(&licenses), 0);
+
+ pkgconf_license_free(&licenses);
+ pkgconf_client_free(client);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_license_insert_and_free);
+ TEST_RUN(basename, test_license_free_empty);
+
+ TEST_RUN(basename, test_license_evaluate_single);
+ TEST_RUN(basename, test_license_evaluate_or);
+ TEST_RUN(basename, test_license_evaluate_and);
+ TEST_RUN(basename, test_license_evaluate_multiple_keys_implicit_and);
+ TEST_RUN(basename, test_license_evaluate_brackets);
+ TEST_RUN(basename, test_license_evaluate_empty);
+ TEST_RUN(basename, test_license_evaluate_sanitizes);
+ TEST_RUN(basename, test_license_evaluate_long_sanitized_token);
+ TEST_RUN(basename, test_license_evaluate_unterminated_quote);
+
+ TEST_RUN(basename, test_license_render_empty);
+ TEST_RUN(basename, test_license_render_single);
+
+ TEST_RUN(basename, test_license_copy_list);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-path-utils.c b/tests/api/test-path-utils.c
new file mode 100644
index 000000000000..519cadb6f639
--- /dev/null
+++ b/tests/api/test-path-utils.c
@@ -0,0 +1,119 @@
+/*
+ * test-path-utils.c
+ * Tests for libpkgconf internal path utility functions.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025-2026 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/path.h>
+#include "test-api.h"
+
+static void
+test_path_find_basename(void)
+{
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("/usr/lib/pkgconfig/foo.pc"), "foo.pc");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("/usr/lib/pkgconfig"), "pkgconfig");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("foo.pc"), "foo.pc");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("/foo.pc"), "foo.pc");
+ TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("/"));
+ TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename(""));
+ TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("/usr/"));
+ TEST_ASSERT_EMPTY_STRING(pkgconf_path_find_basename("usr/"));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("///usr/lib///pkgconfig///foo.pc"), "foo.pc");
+#ifdef _WIN32
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:\\lib\\pkgconfig\\foo.pc"), "foo.pc");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:/lib/pkgconfig/foo.pc"), "foo.pc");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:/lib\\pkgconfig/foo.pc"), "foo.pc");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename("C:\\lib/pkgconfig\\foo.pc"), "foo.pc");
+#endif
+}
+
+static void
+test_path_trim_basename(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&buf, "/usr/lib/pkgconfig/foo.pc");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib/pkgconfig");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "");
+ TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "");
+
+ pkgconf_buffer_reset(&buf);
+ pkgconf_buffer_append(&buf, "foo.pc");
+ TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foo.pc");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+static void
+test_determine_prefix_logic(void)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ // Normal case
+ pkgconf_buffer_append(&buf, "/opt/foo/lib/pkgconfig/bar.pc");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename(pkgconf_buffer_str(&buf)), "pkgconfig");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/opt/foo");
+
+ // Short path: /pkgconfig/foo.pc
+ pkgconf_buffer_reset(&buf);
+ pkgconf_buffer_append(&buf, "/pkgconfig/foo.pc");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename(pkgconf_buffer_str(&buf)), "pkgconfig");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf)); // trims pkgconfig, returns true because of /
+ TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf)); // fails to trim further
+
+ // Another short path: lib/pkgconfig/foo.pc
+ pkgconf_buffer_reset(&buf);
+ pkgconf_buffer_append(&buf, "lib/pkgconfig/foo.pc");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_path_find_basename(pkgconf_buffer_str(&buf)), "pkgconfig");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_FALSE(pkgconf_path_trim_basename(&buf));
+
+ // Trailing slash
+ pkgconf_buffer_reset(&buf);
+ pkgconf_buffer_append(&buf, "/usr/lib/pkgconfig/");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib/pkgconfig");
+ TEST_ASSERT_TRUE(pkgconf_path_trim_basename(&buf));
+ TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "/usr/lib");
+
+ pkgconf_buffer_finalize(&buf);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_path_find_basename);
+ TEST_RUN(basename, test_path_trim_basename);
+ TEST_RUN(basename, test_determine_prefix_logic);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-tuple.c b/tests/api/test-tuple.c
new file mode 100644
index 000000000000..b648abffe3f3
--- /dev/null
+++ b/tests/api/test-tuple.c
@@ -0,0 +1,258 @@
+/*
+ * test-tuple.c
+ * Tests for the public libpkgconf tuple API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "test-api.h"
+
+static void
+test_tuple_add_and_find(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
+
+ /* parse=false means store the value verbatim, no bytecode compile.
+ * That's the right mode for storing already-evaluated values. */
+ pkgconf_tuple_t *t = pkgconf_tuple_add(client, &tuples, "prefix", "/opt/foo", false, 0);
+ TEST_ASSERT_NONNULL(t);
+
+ const char *found = pkgconf_tuple_find(client, &tuples, "prefix");
+ TEST_ASSERT_NONNULL(found);
+ TEST_ASSERT_STRCMP_EQ(found, "/opt/foo");
+
+ pkgconf_tuple_free(&tuples);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_find_absent(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
+
+ const char *found = pkgconf_tuple_find(client, &tuples, "nonexistent");
+ TEST_ASSERT_EMPTY_STRING(found);
+
+ pkgconf_tuple_free(&tuples);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_add_multiple(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_tuple_add(client, &tuples, "prefix", "/usr", false, 0);
+ pkgconf_tuple_add(client, &tuples, "exec_prefix", "/usr/local", false, 0);
+ pkgconf_tuple_add(client, &tuples, "libdir", "/usr/lib", false, 0);
+
+ TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "prefix"), "/usr");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "exec_prefix"), "/usr/local");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "libdir"), "/usr/lib");
+ TEST_ASSERT_EMPTY_STRING(pkgconf_tuple_find(client, &tuples, "datadir"));
+
+ pkgconf_tuple_free(&tuples);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_global_add_and_find(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_tuple_add_global(client, "PKG_TEST_KEY", "test_value");
+
+ const char *found = pkgconf_tuple_find_global(client, "PKG_TEST_KEY");
+ TEST_ASSERT_NONNULL(found);
+ TEST_ASSERT_STRCMP_EQ(found, "test_value");
+
+ pkgconf_tuple_free_global(client);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_global_find_absent(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ const char *found = pkgconf_tuple_find_global(client, "DOES_NOT_EXIST");
+ TEST_ASSERT_EMPTY_STRING(found);
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_define_global_kv_form(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ /* This is the exact path --define-variable=KEY=VALUE takes
+ * pkgconf_tuple_define_global parses the "key=value" form and inserts it as a global tuple. */
+ pkgconf_tuple_define_global(client, "myvar=myvalue");
+
+ const char *found = pkgconf_tuple_find_global(client, "myvar");
+ TEST_ASSERT_NONNULL(found);
+ TEST_ASSERT_STRCMP_EQ(found, "myvalue");
+
+ pkgconf_tuple_free_global(client);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_define_global_with_equals_in_value(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ // Only the first '=' should split key from value; subsequent '=' characters belong to the value.
+ pkgconf_tuple_define_global(client, "CFLAGS=-DFOO=bar");
+
+ const char *found = pkgconf_tuple_find_global(client, "CFLAGS");
+ TEST_ASSERT_NONNULL(found);
+ TEST_ASSERT_STRCMP_EQ(found, "-DFOO=bar");
+
+ pkgconf_tuple_free_global(client);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_global_multiple(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_tuple_define_global(client, "a=1");
+ pkgconf_tuple_define_global(client, "b=2");
+ pkgconf_tuple_define_global(client, "c=3");
+
+ TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "a"), "1");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "b"), "2");
+ TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "c"), "3");
+
+ pkgconf_tuple_free_global(client);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_global_free_resets(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_tuple_define_global(client, "temp=value");
+ TEST_ASSERT_NONNULL(pkgconf_tuple_find_global(client, "temp"));
+
+ pkgconf_tuple_free_global(client);
+ TEST_ASSERT_EMPTY_STRING(pkgconf_tuple_find_global(client, "temp"));
+
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_free_empty(void)
+{
+ pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
+
+ // Freeing an empty list should be a no-op. Mostly an ASan/leak check smoke test.
+ pkgconf_tuple_free(&tuples);
+}
+
+static void
+test_tuple_define_variable_end_to_end(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ pkgconf_tuple_define_global(client, "myprefix=/opt/custom");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "-I${myprefix}/include", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "-I/opt/custom/include");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_tuple_free_global(client);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_define_variable_overrides_local(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix");
+ TEST_ASSERT_NONNULL(v);
+ pkgconf_buffer_reset(&v->bcbuf);
+ pkgconf_bytecode_compile(&v->bcbuf, "/usr");
+ pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+
+ // Simulate user passing --define-variable=prefix=/custom.
+ pkgconf_tuple_define_global(client, "prefix=/custom");
+
+ char *out = pkgconf_bytecode_eval_str(client, &vars, "${prefix}/lib", &saw_sysroot);
+
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/custom/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_tuple_free_global(client);
+ pkgconf_client_free(client);
+}
+
+static void
+test_tuple_escaped_quote(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
+
+ // A double-quoted value containing an escaped double-quote
+ pkgconf_tuple_t *t = pkgconf_tuple_add(client, &tuples, "key", "\"a\\\"b\"", true, 0);
+ TEST_ASSERT_NONNULL(t);
+
+ const char *found = pkgconf_tuple_find(client, &tuples, "key");
+ TEST_ASSERT_NONNULL(found);
+ TEST_ASSERT_STRCMP_EQ(found, "a\"b");
+
+ pkgconf_tuple_free(&tuples);
+ pkgconf_client_free(client);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_tuple_add_and_find);
+ TEST_RUN(basename, test_tuple_find_absent);
+ TEST_RUN(basename, test_tuple_add_multiple);
+ TEST_RUN(basename, test_tuple_free_empty);
+ TEST_RUN(basename, test_tuple_global_add_and_find);
+ TEST_RUN(basename, test_tuple_global_find_absent);
+ TEST_RUN(basename, test_tuple_define_global_kv_form);
+ TEST_RUN(basename, test_tuple_define_global_with_equals_in_value);
+ TEST_RUN(basename, test_tuple_global_multiple);
+ TEST_RUN(basename, test_tuple_global_free_resets);
+ TEST_RUN(basename, test_tuple_define_variable_end_to_end);
+ TEST_RUN(basename, test_tuple_define_variable_overrides_local);
+ TEST_RUN(basename, test_tuple_escaped_quote);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-variable.c b/tests/api/test-variable.c
new file mode 100644
index 000000000000..ec65df3ae5a8
--- /dev/null
+++ b/tests/api/test-variable.c
@@ -0,0 +1,247 @@
+/*
+ * test-variable.c
+ * Tests for the public libpkgconf variable API.
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "test-api.h"
+
+/*
+ * Install a value on a variable by compiling it as bytecode.
+ * Mirrors what the parser does when it reads a .pc field.
+ */
+static void
+seed_variable(pkgconf_list_t *vars, const char *key, const char *value)
+{
+ pkgconf_variable_t *v = pkgconf_variable_get_or_create(vars, key);
+ TEST_ASSERT_NONNULL(v);
+
+ pkgconf_buffer_reset(&v->bcbuf);
+ pkgconf_bytecode_compile(&v->bcbuf, value);
+ pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+}
+
+static void
+test_variable_new_and_free(void)
+{
+ pkgconf_variable_t *v = pkgconf_variable_new("prefix");
+
+ TEST_ASSERT_NONNULL(v);
+ TEST_ASSERT_STRCMP_EQ(v->key, "prefix");
+
+ pkgconf_variable_free(v);
+}
+
+static void
+test_variable_get_or_create_creates(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix");
+ TEST_ASSERT_NONNULL(v);
+ TEST_ASSERT_STRCMP_EQ(v->key, "prefix");
+
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_get_or_create_returns_existing(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_variable_t *first = pkgconf_variable_get_or_create(&vars, "libdir");
+ TEST_ASSERT_NONNULL(first);
+
+ // A second call with the same key should return the same object, not create a duplicate.
+ pkgconf_variable_t *second = pkgconf_variable_get_or_create(&vars, "libdir");
+ TEST_ASSERT_NONNULL(second);
+ TEST_ASSERT_EQ(first, second);
+
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_find_present(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_variable_t *created = pkgconf_variable_get_or_create(&vars, "prefix");
+ TEST_ASSERT_NONNULL(created);
+
+ pkgconf_variable_t *found = pkgconf_variable_find(&vars, "prefix");
+ TEST_ASSERT_NONNULL(found);
+ TEST_ASSERT_EQ(created, found);
+
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_find_absent(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_variable_t *found = pkgconf_variable_find(&vars, "nonexistent");
+ TEST_ASSERT_NULL(found);
+
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_find_among_many(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_variable_get_or_create(&vars, "prefix");
+ pkgconf_variable_get_or_create(&vars, "exec_prefix");
+ pkgconf_variable_get_or_create(&vars, "libdir");
+ pkgconf_variable_get_or_create(&vars, "includedir");
+
+ TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "prefix"));
+ TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "exec_prefix"));
+ TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "libdir"));
+ TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "includedir"));
+ TEST_ASSERT_NULL(pkgconf_variable_find(&vars, "notpresent"));
+
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_delete(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix");
+ TEST_ASSERT_NONNULL(v);
+ TEST_ASSERT_NONNULL(pkgconf_variable_find(&vars, "prefix"));
+
+ pkgconf_variable_delete(&vars, v);
+ TEST_ASSERT_NULL(pkgconf_variable_find(&vars, "prefix"));
+
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_eval_str_plain(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ seed_variable(&vars, "version", "1.2.3");
+
+ pkgconf_variable_t *v = pkgconf_variable_find(&vars, "version");
+ TEST_ASSERT_NONNULL(v);
+
+ char *out = pkgconf_variable_eval_str(client, &vars, v, &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "1.2.3");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_variable_eval_str_with_reference(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ // Standard pkg-config layout: libdir is derived from prefix.
+ seed_variable(&vars, "prefix", "/opt/foo");
+ seed_variable(&vars, "libdir", "${prefix}/lib");
+
+ pkgconf_variable_t *libdir = pkgconf_variable_find(&vars, "libdir");
+ TEST_ASSERT_NONNULL(libdir);
+
+ char *out = pkgconf_variable_eval_str(client, &vars, libdir, &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/opt/foo/lib");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_variable_eval_str_chained(void)
+{
+ pkgconf_client_t *client = test_client_new();
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+ bool saw_sysroot = false;
+
+ seed_variable(&vars, "prefix", "/usr/local");
+ seed_variable(&vars, "exec_prefix", "${prefix}");
+ seed_variable(&vars, "includedir", "${exec_prefix}/include");
+
+ pkgconf_variable_t *includedir = pkgconf_variable_find(&vars, "includedir");
+ TEST_ASSERT_NONNULL(includedir);
+
+ char *out = pkgconf_variable_eval_str(client, &vars, includedir, &saw_sysroot);
+ TEST_ASSERT_NONNULL(out);
+ TEST_ASSERT_STRCMP_EQ(out, "/usr/local/include");
+
+ free(out);
+ pkgconf_variable_list_free(&vars);
+ pkgconf_client_free(client);
+}
+
+static void
+test_variable_list_free_handles_empty(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ // Freeing an empty list should be a no-op and not crash. Mostly an ASan/leak smoke test.
+ pkgconf_variable_list_free(&vars);
+}
+
+static void
+test_variable_list_free_handles_many(void)
+{
+ pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
+
+ /* Add a mix of bare and value-carrying variables, then free the whole list at once.
+ * Mostly an ASan/leak smoke test. */
+ pkgconf_variable_get_or_create(&vars, "a");
+ seed_variable(&vars, "b", "/path/b");
+ pkgconf_variable_get_or_create(&vars, "c");
+ seed_variable(&vars, "d", "${b}/sub");
+
+ pkgconf_variable_list_free(&vars);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_variable_new_and_free);
+ TEST_RUN(basename, test_variable_get_or_create_creates);
+ TEST_RUN(basename, test_variable_get_or_create_returns_existing);
+ TEST_RUN(basename, test_variable_find_present);
+ TEST_RUN(basename, test_variable_find_absent);
+ TEST_RUN(basename, test_variable_find_among_many);
+ TEST_RUN(basename, test_variable_delete);
+ TEST_RUN(basename, test_variable_eval_str_plain);
+ TEST_RUN(basename, test_variable_eval_str_with_reference);
+ TEST_RUN(basename, test_variable_eval_str_chained);
+ TEST_RUN(basename, test_variable_list_free_handles_empty);
+ TEST_RUN(basename, test_variable_list_free_handles_many);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/basic.sh b/tests/basic.sh
deleted file mode 100755
index 8debb32f2b38..000000000000
--- a/tests/basic.sh
+++ /dev/null
@@ -1,391 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- noargs \
- libs \
- libs_cflags \
- libs_cflags_version \
- libs_cflags_version_multiple \
- libs_cflags_version_alt \
- libs_cflags_version_different \
- libs_cflags_version_different_bad \
- libs_env \
- exists_nonexitent \
- nonexitent \
- exists_version \
- exists_version_bad \
- exists_version_bad2 \
- exists_version_bad3 \
- exists \
- exists2 \
- exists3 \
- exists_version_alt \
- exists_cflags \
- exists_cflags_env \
- uninstalled_bad \
- uninstalled \
- libs_intermediary \
- libs_circular1 \
- libs_circular2 \
- libs_circular_directpc \
- libs_static \
- libs_static_ordering \
- libs_metapackage \
- license_isc \
- license_noassertion \
- modversion_noflatten \
- pkg_config_path \
- nolibs \
- nocflags \
- arbitary_path \
- with_path \
- relocatable \
- single_depth_selectors \
- print_variables_env \
- variable_env \
- variable_no_recurse
-
-noargs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check -s exit:1 -e ignore pkgconf
-}
-
-libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lfoo\n" \
- pkgconf --libs foo
-}
-
-libs_cflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \
- pkgconf --cflags --libs foo
-}
-
-atf_test_case basic_libs_cflags_version
-libs_cflags_version_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \
- pkgconf --cflags --libs 'foo > 1.2'
-}
-
-libs_cflags_version_multiple_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lbar -lfoo\n" \
- pkgconf --cflags --libs 'foo > 1.2 bar >= 1.3'
-}
-
-libs_cflags_version_multiple_coma_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lbar -lfoo\n" \
- pkgconf --cflags --libs 'foo > 1.2,bar >= 1.3'
-}
-
-libs_cflags_version_alt_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \
- pkgconf --cflags --libs 'foo' '>' '1.2'
-}
-
-libs_cflags_version_different_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \
- pkgconf --cflags --libs 'foo' '!=' '1.3.0'
-}
-
-atf_test_case basic_libs_cflags_version_different_bad
-libs_cflags_version_different_bad_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- -e inline:"Package dependency requirement 'foo != 1.2.3' could not be satisfied.\nPackage 'foo' has version '1.2.3', required version is '!= 1.2.3'\n" \
- pkgconf --cflags --libs 'foo' '!=' '1.2.3'
-}
-
-exists_nonexitent_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --exists nonexistant
-}
-
-nonexitent_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf nonexistant
-}
-
-exists_version_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --exists 'foo > 1.2'
-}
-
-exists_version_bad_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --exists 'foo > 1.2.3'
-}
-
-exists_version_alt_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --exists 'foo' '>' '1.2'
-}
-
-uninstalled_bad_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --uninstalled 'foo'
-}
-
-uninstalled_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --uninstalled 'omg'
-}
-
-exists_version_bad2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --exists 'foo >= '
-}
-
-exists_version_bad3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --exists 'tilde >= 1.0.0'
-}
-
-exists_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --exists 'tilde = 1.0.0~rc1'
-}
-
-exists2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --exists 'tilde <= 1.0.0'
-}
-
-exists3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --exists '' 'foo'
-}
-
-libs_intermediary_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lintermediary-1 -lintermediary-2 -lfoo -lbar -lbaz\n" \
- pkgconf --libs intermediary-1 intermediary-2
-}
-
-libs_circular2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"circular-1: breaking circular reference (circular-1 -> circular-2 -> circular-1)\n" \
- pkgconf circular-2 --validate
-}
-
-libs_circular1_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"circular-3: breaking circular reference (circular-3 -> circular-1 -> circular-3)\n" \
- pkgconf circular-1 --validate
-}
-
-libs_circular_directpc_body()
-{
- atf_check \
- -o inline:"-lcircular-3 -lcircular-1 -lcircular-2\n" \
- pkgconf --libs ${selfdir}/lib1/circular-3.pc
-}
-
-libs_static_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"/libfoo.a -pthread\n" \
- pkgconf --libs static-archive-libs
-}
-
-libs_static_ordering_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lbar -lfoo\n" \
- pkgconf --libs foo bar
-}
-
-libs_metapackage_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lbar -lfoo\n" \
- pkgconf --static --libs metapackage-3
-}
-
-pkg_config_path_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1${PATH_SEP}${selfdir}/lib2"
- atf_check \
- -o inline:"-L/test/lib -lfoo\n" \
- pkgconf --libs foo
- atf_check \
- -o inline:"-L/test/lib -lbar -lfoo\n" \
- pkgconf --libs bar
-}
-
-with_path_body()
-{
- atf_check \
- -o inline:"-L/test/lib -lfoo\n" \
- pkgconf --with-path=${selfdir}/lib1 --with-path=${selfdir}/lib2 --libs foo
- atf_check \
- -o inline:"-L/test/lib -lbar -lfoo\n" \
- pkgconf --with-path=${selfdir}/lib1 --with-path=${selfdir}/lib2 --libs bar
-}
-
-nolibs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"\n" \
- pkgconf --libs nolib
-}
-
-nocflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"\n" \
- pkgconf --cflags nocflag
-}
-
-arbitary_path_body()
-{
- cp ${selfdir}/lib1/foo.pc .
- atf_check \
- -o inline:"-L/test/lib -lfoo\n" \
- pkgconf --libs foo.pc
-}
-
-relocatable_body()
-{
- basedir=$(pkgconf --relocate ${selfdir})
- atf_check \
- -o inline:"${basedir}/lib-relocatable\n" \
- pkgconf --define-prefix --variable=prefix ${basedir}/lib-relocatable/lib/pkgconfig/foo.pc
-}
-
-single_depth_selectors_body()
-{
- export PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH=1
- atf_check \
- -o inline:"foo\n" \
- pkgconf --with-path=${selfdir}/lib3 --print-requires bar
-}
-
-license_isc_body()
-{
- atf_check \
- -o inline:"foo: ISC\n" \
- pkgconf --with-path=${selfdir}/lib1 --license foo
-}
-
-license_noassertion_body()
-{
- atf_check \
- -o inline:"bar: NOASSERTION\nfoo: ISC\n" \
- pkgconf --with-path=${selfdir}/lib1 --license bar
-}
-
-modversion_noflatten_body()
-{
- atf_check \
- -o inline:"1.3\n" \
- pkgconf --with-path=${selfdir}/lib1 --modversion bar
-}
-
-exists_cflags_body()
-{
- atf_check \
- -o inline:"-DHAVE_FOO\n" \
- pkgconf --with-path=${selfdir}/lib1 --cflags --exists-cflags --fragment-filter=D foo
-}
-
-exists_cflags_env_body()
-{
- atf_check \
- -o inline:"FOO_CFLAGS='-DHAVE_FOO'\n" \
- pkgconf --with-path=${selfdir}/lib1 --cflags --exists-cflags --fragment-filter=D --env=FOO foo
-}
-
-libs_env_body()
-{
- atf_check \
- -o inline:"FOO_LIBS='-L/test/lib -lfoo'\n" \
- pkgconf --with-path=${selfdir}/lib1 --libs --env=FOO foo
-}
-
-print_variables_env_body()
-{
- atf_check \
- -o inline:"FOO_CFLAGS='-fPIC -I/test/include/foo'\nFOO_LIBS='-L/test/lib -lfoo'\nFOO_INCLUDEDIR='/test/include'\nFOO_LIBDIR='/test/lib'\nFOO_EXEC_PREFIX='/test'\nFOO_PREFIX='/test'\nFOO_PCFILEDIR='${selfdir}/lib1'\n" \
- pkgconf --with-path=${selfdir}/lib1 --env=FOO --print-variables --cflags --libs foo
-
-}
-
-variable_env_body()
-{
- atf_check \
- -o inline:"FOO_INCLUDEDIR='/test/include'\n" \
- pkgconf --with-path=${selfdir}/lib1 --env=FOO --variable=includedir foo
-}
-
-variable_no_recurse_body()
-{
- atf_check \
- -o inline:"/test/include\n" \
- pkgconf --with-path=${selfdir}/lib1 --variable=includedir bar
-}
diff --git a/tests/builtins.sh b/tests/builtins.sh
deleted file mode 100755
index cbdea37e0c5f..000000000000
--- a/tests/builtins.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- modversion \
- variable \
- define_variable \
- global_variable
-
-modversion_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"1.0.1 \n" \
- pkgconf --modversion pkg-config
-}
-
-variable_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"/test \n" \
- pkgconf --variable=prefix foo
-}
-
-define_variable_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"/test2 \n" \
- pkgconf --define-variable=prefix=/test2 --variable=prefix foo
-}
-
-global_variable_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"${selfdir}/lib1 \n"
- pkgconf --exists -foo
-}
-
-argv_parse_3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-llib-1 -pthread /test/lib/lib2.so \n" \
- pkgconf --libs argv-parse-3
-}
-
-tilde_quoting_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L~ -ltilde \n" \
- pkgconf --libs tilde-quoting
- atf_check \
- -o inline:"-I~ \n" \
- pkgconf --cflags tilde-quoting
-}
-
-paren_quoting_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L\$(libdir) -ltilde \n" \
- pkgconf --libs paren-quoting
-}
diff --git a/tests/conflicts.sh b/tests/conflicts.sh
deleted file mode 100755
index da6396f6b42c..000000000000
--- a/tests/conflicts.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- libs \
- ignore
-
-libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lconflicts\n" \
- pkgconf --libs conflicts
-}
-
-ignore_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lconflicts\n" \
- pkgconf --ignore-conflicts --libs conflicts
-}
diff --git a/tests/framework.sh b/tests/framework.sh
deleted file mode 100755
index 367692335746..000000000000
--- a/tests/framework.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- libs
-
-libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-F/test/lib -framework framework-1\n" \
- pkgconf --libs framework-1
- atf_check \
- -o inline:"-F/test/lib -framework framework-2 -framework framework-1\n" \
- pkgconf --libs framework-2
- atf_check \
- -o inline:"-F/test/lib -framework framework-2 -framework framework-1\n" \
- pkgconf --libs framework-1 framework-2
-}
diff --git a/tests/lib-sbom-files/basic-spdx-base-id.json b/tests/lib-sbom-files/basic-spdx-base-id.json
new file mode 100644
index 000000000000..f9c67b9b9cfb
--- /dev/null
+++ b/tests/lib-sbom-files/basic-spdx-base-id.json
@@ -0,0 +1,90 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/Agent/default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "https://distfiles.ariadne.space/pkgconf/Agent/default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "simplelicensing_licenseExpression": "BSD-4-Clause"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/software_Sbom/test3",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://distfiles.ariadne.space/pkgconf/Package/test3"
+ ],
+ "element": [
+ "https://distfiles.ariadne.space/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://distfiles.ariadne.space/pkgconf/Relationship/test3/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/Package/test3",
+ "name": "test3",
+ "originatedBy": [
+ "https://distfiles.ariadne.space/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "Test3 copyright text",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "3.0.0"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "from": "https://distfiles.ariadne.space/pkgconf/Package/test3",
+ "to": [
+ "https://distfiles.ariadne.space/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/Relationship/test3/hasConcludedLicense",
+ "from": "https://distfiles.ariadne.space/pkgconf/Package/test3",
+ "to": [
+ "https://distfiles.ariadne.space/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://distfiles.ariadne.space/pkgconf/spdxDocument/1",
+ "rootElement": [
+ "https://distfiles.ariadne.space/pkgconf/software_Sbom/test3"
+ ],
+ "element": [
+ "https://distfiles.ariadne.space/pkgconf/Agent/default",
+ "https://distfiles.ariadne.space/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "https://distfiles.ariadne.space/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://distfiles.ariadne.space/pkgconf/Relationship/test3/hasConcludedLicense",
+ "https://distfiles.ariadne.space/pkgconf/software_Sbom/test3",
+ "https://distfiles.ariadne.space/pkgconf/Package/test3"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/basic-use-uri.json b/tests/lib-sbom-files/basic-use-uri.json
new file mode 100644
index 000000000000..e9913a92a764
--- /dev/null
+++ b/tests/lib-sbom-files/basic-use-uri.json
@@ -0,0 +1,90 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:Agent:default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "github.com:pkgconf:pkgconf:Agent:default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause",
+ "simplelicensing_licenseExpression": "BSD-4-Clause"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:software_Sbom:test3",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "github.com:pkgconf:pkgconf:Package:test3"
+ ],
+ "element": [
+ "github.com:pkgconf:pkgconf:Relationship:test3:hasDeclaredLicense",
+ "github.com:pkgconf:pkgconf:Relationship:test3:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:Package:test3",
+ "name": "test3",
+ "originatedBy": [
+ "github.com:pkgconf:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "Test3 copyright text",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "3.0.0"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:Relationship:test3:hasDeclaredLicense",
+ "from": "github.com:pkgconf:pkgconf:Package:test3",
+ "to": [
+ "github.com:pkgconf:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:Relationship:test3:hasConcludedLicense",
+ "from": "github.com:pkgconf:pkgconf:Package:test3",
+ "to": [
+ "github.com:pkgconf:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "github.com:pkgconf:pkgconf:spdxDocument:1",
+ "rootElement": [
+ "github.com:pkgconf:pkgconf:software_Sbom:test3"
+ ],
+ "element": [
+ "github.com:pkgconf:pkgconf:Agent:default",
+ "github.com:pkgconf:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause",
+ "github.com:pkgconf:pkgconf:Relationship:test3:hasDeclaredLicense",
+ "github.com:pkgconf:pkgconf:Relationship:test3:hasConcludedLicense",
+ "github.com:pkgconf:pkgconf:software_Sbom:test3",
+ "github.com:pkgconf:pkgconf:Package:test3"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/basic.json b/tests/lib-sbom-files/basic.json
new file mode 100644
index 000000000000..3b241ff3442d
--- /dev/null
+++ b/tests/lib-sbom-files/basic.json
@@ -0,0 +1,90 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Agent/default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "simplelicensing_licenseExpression": "BSD-4-Clause"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "name": "test3",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "Test3 copyright text",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "3.0.0"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/spdxDocument/1",
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test3"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Agent/default",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/define_variable.json b/tests/lib-sbom-files/define_variable.json
new file mode 100644
index 000000000000..bef19f2f3de1
--- /dev/null
+++ b/tests/lib-sbom-files/define_variable.json
@@ -0,0 +1,90 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Agent/default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "simplelicensing_licenseExpression": "BSD-2-Clause"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test1",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/variable-test1"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/variable-test1",
+ "name": "variable-test1",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/releases/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "1.0-123"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test1",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test1",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/spdxDocument/1",
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test1"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Agent/default",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test1",
+ "https://github.com/pkgconf/pkgconf/Package/variable-test1"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/define_variable_with_dependency.json b/tests/lib-sbom-files/define_variable_with_dependency.json
new file mode 100644
index 000000000000..cad5b0b269b3
--- /dev/null
+++ b/tests/lib-sbom-files/define_variable_with_dependency.json
@@ -0,0 +1,162 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Agent/default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT",
+ "simplelicensing_licenseExpression": "MIT"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "simplelicensing_licenseExpression": "BSD-2-Clause"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test2",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/variable-test2"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/dependsOn/variable-test1",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test1",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/variable-test1"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/variable-test2",
+ "name": "variable-test2",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/releases/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "2.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/variable-test1",
+ "name": "variable-test1",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/releases/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "1.0-123"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/dependsOn/variable-test1",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/variable-test1"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test1",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/variable-test1",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/spdxDocument/1",
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test2",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test1"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Agent/default",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/dependsOn/variable-test1",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test2/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/variable-test1/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test2",
+ "https://github.com/pkgconf/pkgconf/Package/variable-test2",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/variable-test1",
+ "https://github.com/pkgconf/pkgconf/Package/variable-test1"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/meta_package-use-uri-spdx-base-id.json b/tests/lib-sbom-files/meta_package-use-uri-spdx-base-id.json
new file mode 100644
index 000000000000..ad18ded896c3
--- /dev/null
+++ b/tests/lib-sbom-files/meta_package-use-uri-spdx-base-id.json
@@ -0,0 +1,544 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Agent:default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-2-Clause",
+ "simplelicensing_licenseExpression": "BSD-2-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-1-Clause",
+ "simplelicensing_licenseExpression": "BSD-1-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause",
+ "simplelicensing_licenseExpression": "BSD-4-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:MIT",
+ "simplelicensing_licenseExpression": "MIT"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:Apache-2.0",
+ "simplelicensing_licenseExpression": "Apache-2.0"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:meta_package",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:meta_package"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test1",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test2",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test3",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test4",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test5",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test6",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:test1",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:test1"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:test1:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test1:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:test2",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:test2"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:test2:dependsOn:test3",
+ "distfiles.ariadne.space:pkgconf:Relationship:test2:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test2:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:test3",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:test3"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:test3:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test3:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:test4",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:test4"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:dependsOn:test5",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:dependsOn:test6",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:test5",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:test5"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:test5:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test5:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:software_Sbom:test6",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:Package:test6"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Relationship:test6:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test6:hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "name": "meta_package",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "1.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:test1",
+ "name": "test1",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "Test1 copyright text",
+ "software_homePage": "",
+ "software_downloadLocation": "",
+ "software_packageVersion": "1.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:test2",
+ "name": "test2",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "2.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:test3",
+ "name": "test3",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "Test3 copyright text",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "3.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:test4",
+ "name": "test4",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "4.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:test5",
+ "name": "test5",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "5.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Package:test6",
+ "name": "test6",
+ "originatedBy": [
+ "distfiles.ariadne.space:pkgconf:Agent:default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/releases/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "6.0.0"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test1",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test1"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test2",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test2"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test3",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test3"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test4",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test4"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test5",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test5"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test6",
+ "from": "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test6"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test1:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test1",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-1-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test1:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test1",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-1-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test2:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test2",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test2:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test2",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test2:dependsOn:test3",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test2",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test3"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test3:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test3",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test3:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test3",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test4:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test4",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:MIT"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test4:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test4",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:MIT"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test4:dependsOn:test5",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test4",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test5"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test4:dependsOn:test6",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test4",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:Package:test6"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test5:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test5",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:MIT"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test5:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test5",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:MIT"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test6:hasDeclaredLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test6",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:Apache-2.0"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:Relationship:test6:hasConcludedLicense",
+ "from": "distfiles.ariadne.space:pkgconf:Package:test6",
+ "to": [
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:Apache-2.0"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "distfiles.ariadne.space:pkgconf:spdxDocument:1",
+ "rootElement": [
+ "distfiles.ariadne.space:pkgconf:software_Sbom:meta_package",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test1",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test2",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test3",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test4",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test5",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test6"
+ ],
+ "element": [
+ "distfiles.ariadne.space:pkgconf:Agent:default",
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-2-Clause",
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-1-Clause",
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:BSD-4-Clause",
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:MIT",
+ "distfiles.ariadne.space:pkgconf:simplelicensing_LicenseExpression:Apache-2.0",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test1",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test2",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test3",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test4",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test5",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:dependsOn:test6",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:meta_package:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test1:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test1:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test2:dependsOn:test3",
+ "distfiles.ariadne.space:pkgconf:Relationship:test2:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test2:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test3:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test3:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:dependsOn:test5",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:dependsOn:test6",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test4:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test5:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test5:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test6:hasDeclaredLicense",
+ "distfiles.ariadne.space:pkgconf:Relationship:test6:hasConcludedLicense",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:meta_package",
+ "distfiles.ariadne.space:pkgconf:Package:meta_package",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test1",
+ "distfiles.ariadne.space:pkgconf:Package:test1",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test2",
+ "distfiles.ariadne.space:pkgconf:Package:test2",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test3",
+ "distfiles.ariadne.space:pkgconf:Package:test3",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test4",
+ "distfiles.ariadne.space:pkgconf:Package:test4",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test5",
+ "distfiles.ariadne.space:pkgconf:Package:test5",
+ "distfiles.ariadne.space:pkgconf:software_Sbom:test6",
+ "distfiles.ariadne.space:pkgconf:Package:test6"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/meta_package.json b/tests/lib-sbom-files/meta_package.json
new file mode 100644
index 000000000000..248d663a6a1e
--- /dev/null
+++ b/tests/lib-sbom-files/meta_package.json
@@ -0,0 +1,544 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Agent/default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "simplelicensing_licenseExpression": "BSD-2-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-1-Clause",
+ "simplelicensing_licenseExpression": "BSD-1-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "simplelicensing_licenseExpression": "BSD-4-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT",
+ "simplelicensing_licenseExpression": "MIT"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/Apache-2.0",
+ "simplelicensing_licenseExpression": "Apache-2.0"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/meta_package",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/meta_package"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test1",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test2",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test3",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test4",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test5",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test6",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test1",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test1"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test1/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test1/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test2",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test2"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/dependsOn/test3",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test4",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test4"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/dependsOn/test5",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/dependsOn/test6",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test5",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test5"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test5/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test5/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test6",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test6"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test6/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test6/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "name": "meta_package",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "1.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test1",
+ "name": "test1",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "Test1 copyright text",
+ "software_homePage": "",
+ "software_downloadLocation": "",
+ "software_packageVersion": "1.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "name": "test2",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "2.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "name": "test3",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "Test3 copyright text",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "3.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test4",
+ "name": "test4",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "4.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test5",
+ "name": "test5",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "5.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test6",
+ "name": "test6",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/releases/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "6.0.0"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test1",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test1"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test2",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test2"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test3",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test4",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test4"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test5",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test5"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test6",
+ "from": "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test6"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test1/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test1",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-1-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test1/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test1",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-1-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test2/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test2/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test2/dependsOn/test3",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test4/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test4",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test4/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test4",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test4/dependsOn/test5",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test4",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test5"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test4/dependsOn/test6",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test4",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test6"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test5/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test5",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test5/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test5",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test6/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test6",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/Apache-2.0"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test6/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test6",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/Apache-2.0"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/spdxDocument/1",
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/software_Sbom/meta_package",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test1",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test2",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test4",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test5",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test6"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Agent/default",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-1-Clause",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/MIT",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/Apache-2.0",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test1",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test2",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test3",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test4",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test5",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/dependsOn/test6",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/meta_package/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test1/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test1/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/dependsOn/test3",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/dependsOn/test5",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/dependsOn/test6",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test4/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test5/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test5/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test6/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test6/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/meta_package",
+ "https://github.com/pkgconf/pkgconf/Package/meta_package",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test1",
+ "https://github.com/pkgconf/pkgconf/Package/test1",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test2",
+ "https://github.com/pkgconf/pkgconf/Package/test2",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "https://github.com/pkgconf/pkgconf/Package/test3",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test4",
+ "https://github.com/pkgconf/pkgconf/Package/test4",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test5",
+ "https://github.com/pkgconf/pkgconf/Package/test5",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test6",
+ "https://github.com/pkgconf/pkgconf/Package/test6"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom-files/with_dependency.json b/tests/lib-sbom-files/with_dependency.json
new file mode 100644
index 000000000000..d2036259550c
--- /dev/null
+++ b/tests/lib-sbom-files/with_dependency.json
@@ -0,0 +1,162 @@
+{
+ "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
+ "@graph": [
+ {
+ "type": "Agent",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Agent/default",
+ "name": "Default"
+ },
+ {
+ "type": "CreationInfo",
+ "@id": "_:creationinfo_1",
+ "created": "test",
+ "createdBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "specVersion": "3.0.1"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "simplelicensing_licenseExpression": "BSD-2-Clause"
+ },
+ {
+ "type": "simplelicensing_LicenseExpression",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "simplelicensing_licenseExpression": "BSD-4-Clause"
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test2",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test2"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/dependsOn/test3",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Sbom",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "software_sbomType": [
+ "build"
+ ],
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense"
+ ]
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "name": "test2",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "NOASSERTION",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "2.0.0"
+ },
+ {
+ "type": "software_Package",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "name": "test3",
+ "originatedBy": [
+ "https://github.com/pkgconf/pkgconf/Agent/default"
+ ],
+ "software_copyrightText": "Test3 copyright text",
+ "software_homePage": "https://github.com/pkgconf/pkgconf/",
+ "software_downloadLocation": "https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz",
+ "software_packageVersion": "3.0.0"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test2/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test2/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test2/dependsOn/test3",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test2",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ],
+ "relationshipType": "dependsOn"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasDeclaredLicense"
+ },
+ {
+ "type": "Relationship",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense",
+ "from": "https://github.com/pkgconf/pkgconf/Package/test3",
+ "to": [
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause"
+ ],
+ "relationshipType": "hasConcludedLicense"
+ },
+ {
+ "type": "SpdxDocument",
+ "creationInfo": "_:creationinfo_1",
+ "spdxId": "https://github.com/pkgconf/pkgconf/spdxDocument/1",
+ "rootElement": [
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test2",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test3"
+ ],
+ "element": [
+ "https://github.com/pkgconf/pkgconf/Agent/default",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-2-Clause",
+ "https://github.com/pkgconf/pkgconf/simplelicensing_LicenseExpression/BSD-4-Clause",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/dependsOn/test3",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test2/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasDeclaredLicense",
+ "https://github.com/pkgconf/pkgconf/Relationship/test3/hasConcludedLicense",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test2",
+ "https://github.com/pkgconf/pkgconf/Package/test2",
+ "https://github.com/pkgconf/pkgconf/software_Sbom/test3",
+ "https://github.com/pkgconf/pkgconf/Package/test3"
+ ]
+ }
+ ]
+}
diff --git a/tests/lib-sbom/meta_package.pc b/tests/lib-sbom/meta_package.pc
new file mode 100644
index 000000000000..f3dd872ad2d5
--- /dev/null
+++ b/tests/lib-sbom/meta_package.pc
@@ -0,0 +1,12 @@
+Name: meta_package
+Description: Meta package to test SBOM creation
+URL: https://github.com/pkgconf/pkgconf
+Version: 1.0.0
+Requires: test1 \
+ test2 \
+ test3 \
+ test4 \
+ test5 \
+ test6
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+License: BSD-2-Clause
diff --git a/tests/lib-sbom/test1.pc b/tests/lib-sbom/test1.pc
new file mode 100644
index 000000000000..2cde40250c3f
--- /dev/null
+++ b/tests/lib-sbom/test1.pc
@@ -0,0 +1,6 @@
+Name: test1
+Description: Test package 1
+Version: 1.0.0
+License: BSD-1-Clause
+Copyright: Test1 copyright text
+Requires: test1
diff --git a/tests/lib-sbom/test2.pc b/tests/lib-sbom/test2.pc
new file mode 100644
index 000000000000..d8c6f5dc9537
--- /dev/null
+++ b/tests/lib-sbom/test2.pc
@@ -0,0 +1,7 @@
+Name: test2
+Description: Test package 2
+URL: https://github.com/pkgconf/pkgconf/
+Version: 2.0.0
+Requires: test3
+License: BSD-2-Clause
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
diff --git a/tests/lib-sbom/test3.pc b/tests/lib-sbom/test3.pc
new file mode 100644
index 000000000000..af65789843c1
--- /dev/null
+++ b/tests/lib-sbom/test3.pc
@@ -0,0 +1,8 @@
+Name: test3
+Description: Test package 3
+URL: https://github.com/pkgconf/pkgconf/
+Version: 3.0.0
+License: BSD-4-Clause
+Copyright: Test3 copyright text
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+
diff --git a/tests/lib-sbom/test4.pc b/tests/lib-sbom/test4.pc
new file mode 100644
index 000000000000..502a5fec8445
--- /dev/null
+++ b/tests/lib-sbom/test4.pc
@@ -0,0 +1,8 @@
+Name: test4
+Description: Test package 4
+URL: https://github.com/pkgconf/pkgconf/
+Version: 4.0.0
+License: MIT
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+Requires: test5 \
+ test6
diff --git a/tests/lib-sbom/test5.pc b/tests/lib-sbom/test5.pc
new file mode 100644
index 000000000000..62e77cc3e05a
--- /dev/null
+++ b/tests/lib-sbom/test5.pc
@@ -0,0 +1,7 @@
+Name: test5
+Description: Test package 5
+URL: https://github.com/pkgconf/pkgconf/
+Version: 5.0.0
+License: MIT
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+
diff --git a/tests/lib-sbom/test6.pc b/tests/lib-sbom/test6.pc
new file mode 100644
index 000000000000..b87c3805ed25
--- /dev/null
+++ b/tests/lib-sbom/test6.pc
@@ -0,0 +1,7 @@
+Name: test6
+Description: Test package 6
+URL: https://github.com/pkgconf/pkgconf/releases/
+Version: 6.0.0
+License: Apache-2.0
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+
diff --git a/tests/lib-sbom/variable-test1.pc b/tests/lib-sbom/variable-test1.pc
new file mode 100644
index 000000000000..51a1a710dc5a
--- /dev/null
+++ b/tests/lib-sbom/variable-test1.pc
@@ -0,0 +1,7 @@
+Name: variable-test1
+Description: Variable test 1
+URL: https://github.com/pkgconf/pkgconf/releases/
+Version: ${VERSION_VARIABLE_TEST}
+License: BSD-2-Clause
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+
diff --git a/tests/lib-sbom/variable-test2.pc b/tests/lib-sbom/variable-test2.pc
new file mode 100644
index 000000000000..52033aba61b9
--- /dev/null
+++ b/tests/lib-sbom/variable-test2.pc
@@ -0,0 +1,7 @@
+Name: variable-test2
+Description: Variable test 2
+URL: https://github.com/pkgconf/pkgconf/releases/
+Version: 2.0
+License: ${license_variable_test}
+Source: https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz
+Requires: variable-test1
diff --git a/tests/lib1/c-comment.pc b/tests/lib1/c-comment.pc
index ed776a1f33e4..84f358066655 100644
--- a/tests/lib1/c-comment.pc
+++ b/tests/lib1/c-comment.pc
@@ -15,3 +15,4 @@ Libs: -L${libdir} -lfoo
Cflags: -fPIC -I${includedir}/foo
Cflags.private: -DFOO_STATIC
License: ISC
+Source: https://foo.bar/foo
diff --git a/tests/lib1/cflags-shared-test.pc b/tests/lib1/cflags-shared-test.pc
new file mode 100644
index 000000000000..a802466af90e
--- /dev/null
+++ b/tests/lib1/cflags-shared-test.pc
@@ -0,0 +1,12 @@
+prefix=/test/local
+exec_prefix=${prefix}
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: cflags-shared-test
+Description: A testing pkg-config file for Cflags.shared
+Version: 1.2.3
+Libs: -L${libdir} -lfoo
+Cflags: -I${includedir}/foo
+Cflags.shared: -DFOO_SHARED
+Cflags.private: -DFOO_STATIC
diff --git a/tests/lib1/dollar-sign-escape-2.pc b/tests/lib1/dollar-sign-escape-2.pc
new file mode 100644
index 000000000000..ccbc6ac28bac
--- /dev/null
+++ b/tests/lib1/dollar-sign-escape-2.pc
@@ -0,0 +1,7 @@
+foo = bar
+
+Name: dollar-sign-escape
+Description: test fixture
+Version: 1.0.0
+
+Cflags: $$foo
diff --git a/tests/lib1/dollar-sign-escape.pc b/tests/lib1/dollar-sign-escape.pc
new file mode 100644
index 000000000000..a3df7c4f5964
--- /dev/null
+++ b/tests/lib1/dollar-sign-escape.pc
@@ -0,0 +1,7 @@
+foo = bar
+
+Name: dollar-sign-escape
+Description: test fixture
+Version: 1.0.0
+
+Cflags: $${foo}
diff --git a/tests/lib1/duplicate-tuple.pc b/tests/lib1/duplicate-tuple.pc
new file mode 100644
index 000000000000..501aa2a2fdd5
--- /dev/null
+++ b/tests/lib1/duplicate-tuple.pc
@@ -0,0 +1,6 @@
+prefix=/foo
+prefix=${prefix}
+
+Name: duplicate-tuple
+Version: 1.2.3
+Description: bla bla
diff --git a/tests/lib1/flag-whitespace-2.pc b/tests/lib1/flag-whitespace-2.pc
new file mode 100644
index 000000000000..0b4d98b1c869
--- /dev/null
+++ b/tests/lib1/flag-whitespace-2.pc
@@ -0,0 +1,5 @@
+Name: flag-whitespace-2
+Version: 0
+Description: test case for whitespace between flags
+URL: https://github.com/pkgconf/pkgconf
+Cflags: -I/includedir
diff --git a/tests/lib1/flag-whitespace.pc b/tests/lib1/flag-whitespace.pc
new file mode 100644
index 000000000000..3da8f10e2641
--- /dev/null
+++ b/tests/lib1/flag-whitespace.pc
@@ -0,0 +1,5 @@
+Name: flag-whitespace
+Version: 0
+Description: test case for whitespace between flags
+URL: https://github.com/pkgconf/pkgconf
+Cflags: -I /includedir
diff --git a/tests/lib1/foo.pc b/tests/lib1/foo.pc
index daef9f9fa956..f12ca65b99d2 100644
--- a/tests/lib1/foo.pc
+++ b/tests/lib1/foo.pc
@@ -10,3 +10,5 @@ Libs: -L${libdir} -lfoo
Cflags: -fPIC -I${includedir}/foo
Cflags.private: -DFOO_STATIC
License: ISC
+Source: https://foo.bar/foo
+License.file: https://foo.bar/foo/COPYING
diff --git a/tests/lib1/foobar.pc b/tests/lib1/foobar.pc
index e9aa84337600..582a8cb66b92 100644
--- a/tests/lib1/foobar.pc
+++ b/tests/lib1/foobar.pc
@@ -10,3 +10,4 @@ Libs: -L${libdir} -lfoobar
Cflags: -fPIC -I${includedir}/foobar
Cflags.private: -DFOOBAR_STATIC
License: ISC
+Source: https://foo.bar/foo
diff --git a/tests/lib1/fragment-group-c.pc b/tests/lib1/fragment-group-c.pc
index f79965be0f1d..42688cf6829c 100644
--- a/tests/lib1/fragment-group-c.pc
+++ b/tests/lib1/fragment-group-c.pc
@@ -1,4 +1,4 @@
Name: fragment-group-c
Version: 1.0
Description: Test fixture for fragment groups
-Libs: -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections
+Libs: -Wl,--start-group -la -lb -lgcc -Wl,--end-group -Wl,--gc-sections
diff --git a/tests/lib1/framework-with-libs.pc b/tests/lib1/framework-with-libs.pc
new file mode 100644
index 000000000000..16e070857ec0
--- /dev/null
+++ b/tests/lib1/framework-with-libs.pc
@@ -0,0 +1,4 @@
+Name: framework-with-libs
+Description: test fixture
+Version: 1
+Libs: -framework Security -lcrypto
diff --git a/tests/lib1/idirafter-ordering.pc b/tests/lib1/idirafter-ordering.pc
index c42f7022736e..090da1c99790 100644
--- a/tests/lib1/idirafter-ordering.pc
+++ b/tests/lib1/idirafter-ordering.pc
@@ -1,4 +1,4 @@
Name: Bad
Description: Demonstrates problems with -idirafter in old pkg-config; see also https://bugs.freedesktop.org/show_bug.cgi?id=23480
Version: 1
-Cflags: -I/opt/bad/include1 -idirafter -I/opt/bad/include2 -I/opt/bad/include3
+Cflags: -I/opt/bad/include1 -idirafter /opt/bad/include4 -I/opt/bad/include2 -I/opt/bad/include3
diff --git a/tests/lib1/isystem-sysroot-prefixed.pc b/tests/lib1/isystem-sysroot-prefixed.pc
new file mode 100644
index 000000000000..236678af57fa
--- /dev/null
+++ b/tests/lib1/isystem-sysroot-prefixed.pc
@@ -0,0 +1,4 @@
+Name: isystem-sysroot-prefixed
+Description: -isystem path already begins with the configured sysroot; sysroot must not be injected a second time (regression test for off-by-one in should_inject_sysroot_child)
+Version: 1
+Cflags: -isystem /s/usr/include
diff --git a/tests/lib1/libs-shared-test.pc b/tests/lib1/libs-shared-test.pc
new file mode 100644
index 000000000000..ce24d47274bc
--- /dev/null
+++ b/tests/lib1/libs-shared-test.pc
@@ -0,0 +1,10 @@
+prefix=/test/local
+exec_prefix=${prefix}
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: libs-shared-test
+Description: A testing pkg-config file for Cflags.shared
+Version: 1.2.3
+Libs.private: libfoo-static.a
+Libs.shared: libfoo-shared.so
diff --git a/tests/lib1/license-malformed.pc b/tests/lib1/license-malformed.pc
new file mode 100644
index 000000000000..7c3ea12152e9
--- /dev/null
+++ b/tests/lib1/license-malformed.pc
@@ -0,0 +1,10 @@
+prefix=/test
+exec_prefix=${prefix}
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: license-malformed
+Description: regression fixture for a License field whose token sanitizes to empty
+Version: 1.0
+Libs: -L${libdir} -llicense-malformed
+License: MIT @@@ ISC
diff --git a/tests/lib1/link-abi-cxx.pc b/tests/lib1/link-abi-cxx.pc
new file mode 100644
index 000000000000..42b77dc231cb
--- /dev/null
+++ b/tests/lib1/link-abi-cxx.pc
@@ -0,0 +1,5 @@
+Name: link-abi-cxx
+Description: a C++ leaf library
+Version: 1.0.0
+Libs: -lcxx
+Link.ABI: C++
diff --git a/tests/lib1/link-abi-multi.pc b/tests/lib1/link-abi-multi.pc
new file mode 100644
index 000000000000..03e1c0979135
--- /dev/null
+++ b/tests/lib1/link-abi-multi.pc
@@ -0,0 +1,5 @@
+Name: link-abi-multi
+Description: a library declaring multiple Link.ABI tags with duplicates and mixed case
+Version: 1.0.0
+Libs: -lmulti
+Link.ABI: c++, Fortran, C++
diff --git a/tests/lib1/link-abi-plainc.pc b/tests/lib1/link-abi-plainc.pc
new file mode 100644
index 000000000000..e72d63c7760a
--- /dev/null
+++ b/tests/lib1/link-abi-plainc.pc
@@ -0,0 +1,4 @@
+Name: link-abi-plainc
+Description: a plain C library with no Link.ABI declaration
+Version: 1.0.0
+Libs: -lplainc
diff --git a/tests/lib1/link-abi-private.pc b/tests/lib1/link-abi-private.pc
new file mode 100644
index 000000000000..df44d8dd19f9
--- /dev/null
+++ b/tests/lib1/link-abi-private.pc
@@ -0,0 +1,5 @@
+Name: link-abi-private
+Description: a library that privately depends on a C++ library
+Version: 1.0.0
+Requires.private: link-abi-cxx
+Libs: -lprivate
diff --git a/tests/lib1/link-abi-public.pc b/tests/lib1/link-abi-public.pc
new file mode 100644
index 000000000000..28b4f4fb6560
--- /dev/null
+++ b/tests/lib1/link-abi-public.pc
@@ -0,0 +1,5 @@
+Name: link-abi-public
+Description: a library that publicly exposes a C++ dependency
+Version: 1.0.0
+Requires: link-abi-cxx
+Libs: -lpublic
diff --git a/tests/lib1/multiline-folding.pc b/tests/lib1/multiline-folding.pc
new file mode 100644
index 000000000000..e545f4e39e8d
--- /dev/null
+++ b/tests/lib1/multiline-folding.pc
@@ -0,0 +1,9 @@
+Name: multiline-folding
+Description: test fixture
+Version: 1.0.0
+
+Cflags: -foo\
+-bar
+
+Libs: -qux\
+ -baz
diff --git a/tests/lib1/requires-shared-dep.pc b/tests/lib1/requires-shared-dep.pc
new file mode 100644
index 000000000000..80045bea0bd3
--- /dev/null
+++ b/tests/lib1/requires-shared-dep.pc
@@ -0,0 +1,10 @@
+prefix=/test/local
+exec_prefix=${prefix}
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: requires-shared-dep
+Description: A dependency only needed for shared linking
+Version: 1.0.0
+Libs: -L${libdir} -lshared-dep
+Cflags: -I${includedir}/shared-dep
diff --git a/tests/lib1/requires-shared.pc b/tests/lib1/requires-shared.pc
new file mode 100644
index 000000000000..287a726c55b8
--- /dev/null
+++ b/tests/lib1/requires-shared.pc
@@ -0,0 +1,11 @@
+prefix=/test/local
+exec_prefix=${prefix}
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: requires-shared
+Description: A testing pkg-config file for Requires.shared
+Version: 1.0.0
+Libs: -L${libdir} -lrequires-shared
+Cflags: -I${includedir}/requires-shared
+Requires.shared: requires-shared-dep
diff --git a/tests/lib1/utf8.pc b/tests/lib1/utf8.pc
deleted file mode 100644
index 6507ec0f25b9..000000000000
--- a/tests/lib1/utf8.pc
+++ /dev/null
@@ -1,10 +0,0 @@
-prefix=/tëst
-exec_prefix=${prefix}
-libdir=${prefix}/lib
-includedir=${prefix}/include
-
-Name: utf8
-Description: Library installed in a prefix with UTF-8
-Version: 0
-Libs: -L${libdir} -lutf8
-Cflags: -I${includedir}
diff --git a/tests/lib1/variable-fragment-expansion.pc b/tests/lib1/variable-fragment-expansion.pc
new file mode 100644
index 000000000000..68e9a5c4dd2c
--- /dev/null
+++ b/tests/lib1/variable-fragment-expansion.pc
@@ -0,0 +1,7 @@
+baselibs = -pthread -lm
+
+Version: 1
+Name: foo
+Description: Repro
+
+Libs: -lfoo ${baselibs}
diff --git a/tests/meson.build b/tests/meson.build
deleted file mode 100644
index 56406c3bb0c8..000000000000
--- a/tests/meson.build
+++ /dev/null
@@ -1,23 +0,0 @@
-configure_file(input: 'Kyuafile.in', output: 'Kyuafile', configuration: cdata)
-configure_file(input: 'test_env.sh.in', output: 'test_env.sh', configuration: cdata)
-
-
-tests = [
- 'basic',
- 'builtins',
- 'conflicts',
- 'framework',
- 'parser',
- 'provides',
- 'regress',
- 'requires',
- 'symlink',
- 'sysroot',
- 'version',
-]
-
-
-# yuck
-foreach test : tests
- test_file = configure_file(input: test + '.sh', output: test, copy: true)
-endforeach
diff --git a/tests/parser.sh b/tests/parser.sh
deleted file mode 100755
index 9d9f9c6ff4ba..000000000000
--- a/tests/parser.sh
+++ /dev/null
@@ -1,372 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- comments \
- comments_in_fields \
- dos \
- no_trailing_newline \
- argv_parse \
- bad_option \
- argv_parse_3 \
- tilde_quoting \
- paren_quoting \
- multiline_field \
- multiline_bogus_header \
- escaped_backslash \
- flag_order_1 \
- flag_order_2 \
- flag_order_3 \
- flag_order_4 \
- quoted \
- variable_whitespace \
- fragment_escaping_1 \
- fragment_escaping_2 \
- fragment_escaping_3 \
- fragment_quoting \
- fragment_quoting_2 \
- fragment_quoting_3 \
- fragment_quoting_5 \
- fragment_quoting_7 \
- fragment_comment \
- msvc_fragment_quoting \
- msvc_fragment_render_cflags \
- tuple_dequote \
- version_with_whitespace \
- version_with_whitespace_2 \
- version_with_whitespace_diagnostic \
- fragment_groups \
- fragment_groups_composite \
- fragment_tree \
- truncated \
- c_comment
-
-comments_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lfoo\n" \
- pkgconf --libs comments
-}
-
-comments_in_fields_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lfoo\n" \
- pkgconf --libs comments-in-fields
-}
-
-dos_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib/dos-lineendings -ldos-lineendings\n" \
- pkgconf --libs dos-lineendings
-}
-
-no_trailing_newline_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-I/test/include/no-trailing-newline\n" \
- pkgconf --cflags no-trailing-newline
-}
-
-argv_parse_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-llib-3 -llib-1 -llib-2 -lpthread\n" \
- pkgconf --libs argv-parse
-}
-
-bad_option_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -e ignore \
- -s eq:1 \
- pkgconf --exists -foo
-}
-
-argv_parse_3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-llib-1 -pthread /test/lib/lib2.so\n" \
- pkgconf --libs argv-parse-3
-}
-
-tilde_quoting_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L~ -ltilde\n" \
- pkgconf --libs tilde-quoting
- atf_check \
- -o inline:"-I~\n" \
- pkgconf --cflags tilde-quoting
-}
-
-paren_quoting_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L\$(libdir) -ltilde\n" \
- pkgconf --libs paren-quoting
-}
-
-multiline_field_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -e ignore \
- -o match:"multiline description" \
- pkgconf --list-all
-}
-
-multiline_bogus_header_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s eq:0 \
- pkgconf --exists multiline-bogus
-}
-
-escaped_backslash_body()
-{
- atf_check \
- -e ignore \
- -o inline:"-IC:\\\\\\\\A\n" \
- pkgconf --with-path=${selfdir}/lib1 --cflags escaped-backslash
-}
-
-quoted_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-DQUOTED=\\\"bla\\\" -DA=\\\"escaped\\ string\\\'\\ literal\\\" -DB=\\\\\\1\$ -DC=bla\n" \
- pkgconf --cflags quotes
-}
-
-flag_order_1_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -Bdynamic -lfoo -Bstatic -lbar\n" \
- pkgconf --libs flag-order-1
-}
-
-flag_order_2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -Bdynamic -lfoo -Bstatic -lbar -lfoo\n" \
- pkgconf --libs flag-order-1 foo
-}
-
-flag_order_3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -Wl,--start-group -lfoo -lbar -Wl,--end-group\n" \
- pkgconf --libs flag-order-3
-}
-
-flag_order_4_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -Wl,--start-group -lfoo -lbar -Wl,--end-group -lfoo\n" \
- pkgconf --libs flag-order-3 foo
-}
-
-variable_whitespace_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-I/test/include\n" \
- pkgconf --cflags variable-whitespace
-}
-
-fragment_quoting_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -DQUOTED=\\\"/test/share/doc\\\"\n" \
- pkgconf --cflags fragment-quoting
-}
-
-fragment_quoting_2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -DQUOTED=/test/share/doc\n" \
- pkgconf --cflags fragment-quoting-2
-}
-
-fragment_quoting_3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -DQUOTED=\\\"/test/share/doc\\\"\n" \
- pkgconf --cflags fragment-quoting-3
-}
-
-fragment_quoting_5_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -DQUOTED=/test/share/doc\n" \
- pkgconf --cflags fragment-quoting-5
-}
-
-fragment_quoting_7_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\\ world\n" \
- pkgconf --cflags fragment-quoting-7
-}
-
-fragment_escaping_1_body()
-{
- atf_check \
- -o inline:"-IC:\\\\\\\\D\\ E\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags fragment-escaping-1
-}
-
-fragment_escaping_2_body()
-{
- atf_check \
- -o inline:"-IC:\\\\\\\\D\\ E\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags fragment-escaping-2
-}
-
-fragment_escaping_3_body()
-{
- atf_check \
- -o inline:"-IC:\\\\\\\\D\\ E\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags fragment-escaping-3
-}
-
-fragment_quoting_7a_body()
-{
- set -x
-
- test_cflags=$(pkgconf --with-path=${selfdir}/lib1 --cflags fragment-quoting-7)
- echo $test_cflags
-# test_cflags='-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\\ world'
-
- cat > test.c <<- __TESTCASE_END__
- int main(int argc, char *argv[]) { return DEFINED_FROM_PKG_CONFIG; }
- __TESTCASE_END__
- cc -o test-fragment-quoting-7 ${test_cflags} ./test.c
- atf_check -e 42 ./test-fragment-quoting-7
- rm -f test.c test-fragment-quoting-7
-
- set +x
-}
-
-
-fragment_comment_body()
-{
- atf_check \
- -o inline:'kuku=\#ttt\n' \
- pkgconf --with-path="${selfdir}/lib1" --cflags fragment-comment
-}
-
-msvc_fragment_quoting_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:'/libpath:"C:\D E" E.lib \n' \
- pkgconf --libs --msvc-syntax fragment-escaping-1
-}
-
-msvc_fragment_render_cflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:'/I/test/include/foo /DFOO_STATIC \n' \
- pkgconf --cflags --static --msvc-syntax foo
-}
-
-tuple_dequote_body()
-{
- atf_check \
- -o inline:'-L/test/lib -lfoo\n' \
- pkgconf --with-path="${selfdir}/lib1" --libs tuple-quoting
-}
-
-version_with_whitespace_body()
-{
- atf_check \
- -o inline:'3.922\n' \
- pkgconf --with-path="${selfdir}/lib1" --modversion malformed-version
-}
-
-version_with_whitespace_2_body()
-{
- atf_check \
- -o inline:'malformed-version = 3.922\n' \
- pkgconf --with-path="${selfdir}/lib1" --print-provides malformed-version
-}
-
-version_with_whitespace_diagnostic_body()
-{
- atf_check \
- -o match:warning \
- pkgconf --with-path="${selfdir}/lib1" --validate malformed-version
-}
-
-fragment_groups_body()
-{
- atf_check \
- -o inline:'-Wl,--start-group -la -lb -Wl,--end-group -nodefaultlibs -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections\n' \
- pkgconf --with-path="${selfdir}/lib1" --libs fragment-groups
-}
-
-fragment_groups_composite_body()
-{
- atf_check \
- -o inline:'-Wl,--start-group -la -lb -Wl,--end-group -nodefaultlibs -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections\n' \
- pkgconf --with-path="${selfdir}/lib1" --libs fragment-groups-2
-}
-
-truncated_body()
-{
- atf_check \
- -o match:warning -s exit:1 \
- pkgconf --with-path="${selfdir}/lib1" --validate truncated
-}
-
-c_comment_body()
-{
- atf_check \
- -o match:warning \
- pkgconf --with-path="${selfdir}/lib1" --validate c-comment
-}
-
-fragment_tree_body()
-{
- atf_check \
- -o inline:"'-Wl,--start-group' [untyped]
- '-la' [type l]
- '-lb' [type l]
- '-Wl,--end-group' [untyped]
-
-'-nodefaultlibs' [untyped]
-'-Wl,--start-group' [untyped]
- '-la' [type l]
- '-lgcc' [type l]
- '-Wl,--end-group' [untyped]
-
-'-Wl,--gc-sections' [untyped]
-
-" \
- pkgconf --with-path="${selfdir}/lib1" --fragment-tree fragment-groups-2
-}
-
diff --git a/tests/personality-data/i386-linux-gnu.personality b/tests/personality-data/i386-linux-gnu.personality
new file mode 100644
index 000000000000..fbfde13da012
--- /dev/null
+++ b/tests/personality-data/i386-linux-gnu.personality
@@ -0,0 +1,4 @@
+Triplet: i386-linux-gnu
+DefaultSearchPaths: /usr/lib/i386-linux-gnu/pkgconfig:/usr/share/pkgconfig
+SystemIncludePaths: /usr/lib/i386-linux-gnu/include
+SystemLibraryPaths: /usr/lib/i386-linux-gnu/lib
diff --git a/tests/provides.sh b/tests/provides.sh
deleted file mode 100755
index b699dbf563f1..000000000000
--- a/tests/provides.sh
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- simple \
- foo \
- bar \
- baz \
- quux \
- moo \
- meow \
- indirect_dependency_node
-
-simple_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
-OUTPUT="provides-test-foo = 1.0.0
-provides-test-bar > 1.1.0
-provides-test-baz >= 1.1.0
-provides-test-quux < 1.2.0
-provides-test-moo <= 1.2.0
-provides-test-meow != 1.3.0
-provides = 1.2.3
-"
- atf_check \
- -o inline:"${OUTPUT}" \
- pkgconf --print-provides provides
- atf_check \
- -o inline:"-lfoo\n" \
- pkgconf --libs provides-request-simple
- atf_check \
- -e ignore \
- -s exit:1 \
- pkgconf --no-provides --libs provides-request-simple
-}
-
-foo_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o ignore \
- pkgconf --libs provides-test-foo
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-foo = 1.0.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-foo >= 1.0.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-foo <= 1.0.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-foo != 1.0.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-foo > 1.0.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-foo < 1.0.0'
-}
-
-bar_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o ignore \
- pkgconf --libs provides-test-bar
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-bar = 1.1.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-bar >= 1.1.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-bar <= 1.1.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-bar != 1.1.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-bar != 1.1.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-bar > 1.1.1'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-bar <= 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-bar <= 1.2.0'
-}
-
-baz_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o ignore \
- pkgconf --libs provides-test-baz
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz = 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz >= 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz <= 1.1.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-baz != 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz != 1.0.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz > 1.1.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz > 1.1.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-baz < 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-baz < 1.2.0'
-}
-
-quux_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o ignore \
- pkgconf --libs provides-test-quux
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux = 1.1.9'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux >= 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux >= 1.1.9'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-quux >= 1.2.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-quux <= 1.2.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux <= 1.1.9'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux != 1.2.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-quux != 1.1.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-quux != 1.0.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux > 1.1.9'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-quux > 1.2.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-quux < 1.1.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-quux > 1.2.0'
-}
-
-moo_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o ignore \
- pkgconf --libs provides-test-moo
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo = 1.2.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo >= 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo >= 1.2.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-moo >= 1.2.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo <= 1.2.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-moo != 1.1.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-moo != 1.0.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo > 1.1.9'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-moo > 1.2.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo < 1.1.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-moo < 1.2.0'
-}
-
-meow_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o ignore \
- pkgconf --libs provides-test-meow
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-meow = 1.3.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-meow != 1.3.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-meow > 1.2.9'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-meow < 1.3.1'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-meow < 1.3.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-meow > 1.3.0'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-meow >= 1.3.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-meow >= 1.3.1'
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --libs 'provides-test-meow <= 1.3.0'
- atf_check \
- -o ignore \
- pkgconf --libs 'provides-test-meow < 1.2.9'
-}
-
-indirect_dependency_node_body()
-{
- atf_check \
- -o inline:'1.2.3\n' \
- pkgconf --with-path="${selfdir}/lib1" --modversion 'provides-test-meow'
- atf_check \
- -s exit:1 \
- -e ignore \
- pkgconf --with-path="${selfdir}/lib1" --modversion 'provides-test-meow = 1.3.0'
-}
diff --git a/tests/regress.sh b/tests/regress.sh
deleted file mode 100755
index b264f3593f90..000000000000
--- a/tests/regress.sh
+++ /dev/null
@@ -1,354 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- case_sensitivity \
- depgraph_break_1 \
- depgraph_break_2 \
- depgraph_break_3 \
- define_variable \
- define_variable_override \
- variable \
- keep_system_libs \
- libs \
- libs_only \
- libs_never_mergeback \
- cflags_only \
- cflags_never_mergeback \
- incomplete_libs \
- incomplete_cflags \
- isystem_munge_order \
- isystem_munge_sysroot \
- idirafter_munge_order \
- idirafter_munge_sysroot \
- idirafter_ordering \
- modversion_common_prefix \
- modversion_fullpath \
- modversion_provides \
- modversion_uninstalled \
- modversion_one_word_expression \
- modversion_two_word_expression \
- modversion_three_word_expression \
- modversion_one_word_expression_no_space \
- modversion_one_word_expression_no_space_zero \
- pcpath \
- virtual_variable \
- fragment_collision \
- malformed_1 \
- malformed_quoting \
- explicit_sysroot \
- empty_tuple \
- solver_requires_private_debounce \
- billion_laughs \
- define_prefix_child_prefix_1 \
- define_prefix_child_prefix_1_env
-
-# sysroot_munge \
-
-case_sensitivity_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"3\n" \
- pkgconf --variable=foo case-sensitivity
- atf_check \
- -o inline:"4\n" \
- pkgconf --variable=Foo case-sensitivity
-}
-
-depgraph_break_1_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check -s exit:1 -e ignore \
- pkgconf --exists --print-errors 'foo > 0.6.0 foo < 0.8.0'
-}
-
-depgraph_break_2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check -s exit:1 -e ignore \
- pkgconf --exists --print-errors 'nonexisting foo <= 3'
-}
-
-depgraph_break_3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check -s exit:1 -e ignore \
- pkgconf --exists --print-errors 'depgraph-break'
-}
-
-define_variable_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check -o inline:"\\\${libdir}/typelibdir\n" \
- pkgconf --variable=typelibdir --define-variable='libdir=\${libdir}' typelibdir
-}
-
-define_variable_override_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check -o inline:"/test\n" \
- pkgconf --variable=prefix --define-variable='prefix=/test' typelibdir
-}
-
-variable_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"/test/include\n" \
- pkgconf --variable=includedir foo
-}
-
-keep_system_libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- eval export "$LIBRARY_PATH_ENV"="/test/local/lib"
- atf_check \
- -o inline:"\n" \
- pkgconf --libs-only-L cflags-libs-only
-
- atf_check \
- -o inline:"-L/test/local/lib\n" \
- pkgconf --libs-only-L --keep-system-libs cflags-libs-only
-}
-
-libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/local/lib -lfoo\n" \
- pkgconf --libs cflags-libs-only
-}
-
-libs_only_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/local/lib -lfoo\n" \
- pkgconf --libs-only-L --libs-only-l cflags-libs-only
-}
-
-libs_never_mergeback_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/bar/lib -lfoo1\n" \
- pkgconf --libs prefix-foo1
- atf_check \
- -o inline:"-L/test/bar/lib -lfoo1 -lfoo2\n" \
- pkgconf --libs prefix-foo1 prefix-foo2
-}
-
-cflags_only_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-I/test/local/include/foo\n" \
- pkgconf --cflags-only-I --cflags-only-other cflags-libs-only
-}
-
-cflags_never_mergeback_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-I/test/bar/include/foo -DBAR -fPIC -DFOO\n" \
- pkgconf --cflags prefix-foo1 prefix-foo2
-}
-
-incomplete_libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"\n" \
- pkgconf --libs incomplete
-}
-
-incomplete_cflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"\n" \
- pkgconf --cflags incomplete
-}
-
-isystem_munge_order_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-isystem /opt/bad/include -isystem /opt/bad2/include\n" \
- pkgconf --cflags isystem
-}
-
-isystem_munge_sysroot_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1" PKG_CONFIG_SYSROOT_DIR="${selfdir}"
- atf_check \
- -o match:"-isystem ${selfdir}/opt/bad/include" \
- pkgconf --cflags isystem
-}
-
-idirafter_munge_order_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-idirafter /opt/bad/include -idirafter /opt/bad2/include\n" \
- pkgconf --cflags idirafter
-}
-
-idirafter_munge_sysroot_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1" PKG_CONFIG_SYSROOT_DIR="${selfdir}"
- atf_check \
- -o match:"-idirafter ${selfdir}/opt/bad/include" \
- pkgconf --cflags idirafter
-}
-
-idirafter_ordering_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-I/opt/bad/include1 -idirafter -I/opt/bad/include2 -I/opt/bad/include3\n" \
- pkgconf --cflags idirafter-ordering
-}
-
-pcpath_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib2"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo\n" \
- pkgconf --cflags ${selfdir}/lib3/bar.pc
-}
-
-sysroot_munge_body()
-{
- sed "s|/sysroot/|${selfdir}/|g" ${selfdir}/lib1/sysroot-dir.pc > ${selfdir}/lib1/sysroot-dir-selfdir.pc
- export PKG_CONFIG_PATH="${selfdir}/lib1" PKG_CONFIG_SYSROOT_DIR="${selfdir}"
- atf_check \
- -o inline:"-L${selfdir}/lib -lfoo\n" \
- pkgconf --libs sysroot-dir-selfdir
-}
-
-virtual_variable_body()
-{
- atf_check -s exit:0 \
- pkgconf --exists pkg-config
- atf_check -s exit:0 \
- pkgconf --exists pkgconf
-
- atf_check -o inline:"${pcpath}\n" \
- pkgconf --variable=pc_path pkg-config
- atf_check -o inline:"${pcpath}\n" \
- pkgconf --variable=pc_path pkgconf
-}
-
-fragment_collision_body()
-{
- atf_check -o inline:"-D_BAZ -D_BAR -D_FOO -D_THREAD_SAFE -pthread\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags fragment-collision
-}
-
-malformed_1_body()
-{
- atf_check -s exit:1 -o ignore \
- pkgconf --validate --with-path="${selfdir}/lib1" malformed-1
-}
-
-malformed_quoting_body()
-{
- atf_check -s exit:0 -o ignore \
- pkgconf --validate --with-path="${selfdir}/lib1" malformed-quoting
-}
-
-explicit_sysroot_body()
-{
- export PKG_CONFIG_SYSROOT_DIR=${selfdir}
- atf_check -o inline:"${selfdir}/usr/share/test\n" \
- pkgconf --with-path="${selfdir}/lib1" --variable=pkgdatadir explicit-sysroot
-}
-
-empty_tuple_body()
-{
- atf_check -o inline:"\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags empty-tuple
-}
-
-solver_requires_private_debounce_body()
-{
- atf_check -o inline:"-I/metapackage-1 -I/metapackage-2 -lmetapackage-1 -lmetapackage-2\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags --libs metapackage
-}
-
-billion_laughs_body()
-{
- atf_check -o inline:"warning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\n" \
- pkgconf --with-path="${selfdir}/lib1" --validate billion-laughs
-}
-
-modversion_common_prefix_body()
-{
- atf_check -o inline:"foo: 1.2.3\nfoobar: 3.2.1\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion --verbose foo foobar
-}
-
-modversion_fullpath_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --modversion "${selfdir}/lib1/foo.pc"
-}
-
-modversion_provides_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion unavailable
-}
-
-modversion_uninstalled_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion omg
-}
-
-modversion_one_word_expression_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion "foo > 1.0"
-}
-
-modversion_two_word_expression_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion foo "> 1.0"
-}
-
-modversion_three_word_expression_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion foo ">" 1.0
-}
-
-modversion_one_word_expression_no_space_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion "foo >1.0"
-}
-
-modversion_one_word_expression_no_space_zero_body()
-{
- atf_check -o inline:"1.2.3\n" \
- pkgconf --with-path="${selfdir}/lib1" --modversion "foo >0.5"
-}
-
-define_prefix_child_prefix_1_body()
-{
- atf_check -o inline:"-I${selfdir}/lib1/include/child-prefix-1 -L${selfdir}/lib1/lib64 -lchild-prefix-1\n" \
- pkgconf --with-path="${selfdir}/lib1/child-prefix/pkgconfig" --define-prefix --cflags --libs child-prefix-1
-}
-
-define_prefix_child_prefix_1_env_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1/child-prefix/pkgconfig"
- export PKG_CONFIG_RELOCATE_PATHS=1
- atf_check -o inline:"-I${selfdir}/lib1/include/child-prefix-1 -L${selfdir}/lib1/lib64 -lchild-prefix-1\n" \
- pkgconf --cflags --libs child-prefix-1
-}
diff --git a/tests/requires.sh b/tests/requires.sh
deleted file mode 100755
index 7a95a66cd232..000000000000
--- a/tests/requires.sh
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- libs \
- libs_cflags \
- libs_static \
- libs_static_pure \
- cflags_libs_private \
- argv_parse2 \
- static_cflags \
- private_duplication \
- private_duplication_digraph \
- foo_bar \
- bar_foo \
- foo_metapackage_3 \
- libs_static2 \
- missing \
- requires_internal \
- requires_internal_missing \
- requires_internal_collision \
- orphaned_requires_private
-
-libs_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lbar -lfoo\n" \
- pkgconf --libs bar
-}
-
-libs_cflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -L/test/lib -lbaz\n" \
- pkgconf --libs --cflags baz
-}
-
-libs_static_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lbaz -L/test/lib -lzee -lfoo\n" \
- pkgconf --static --libs baz
-}
-
-libs_static_pure_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-L/test/lib -lbaz -lfoo\n" \
- pkgconf --static --pure --libs baz
-}
-
-argv_parse2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-llib-1 -pthread /test/lib/lib2.so\n" \
- pkgconf --static --libs argv-parse-2
-}
-
-static_cflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo -DFOO_STATIC\n" \
- pkgconf --static --cflags baz
-}
-
-private_duplication_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lprivate -lbaz -lzee -lbar -lfoo\n" \
- pkgconf --static --libs-only-l private-libs-duplication
-}
-
-private_duplication_digraph_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o 'match:"user:request" -> "private-libs-duplication"' \
- -o 'match:"private-libs-duplication" -> "bar"' \
- -o 'match:"private-libs-duplication" -> "baz"' \
- -o 'match:"bar" -> "foo"' \
- -o 'match:"baz" -> "foo"' \
- pkgconf --static --libs-only-l private-libs-duplication --digraph
-}
-
-bar_foo_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lbar -lfoo\n" \
- pkgconf --static --libs-only-l bar foo
-}
-
-foo_bar_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lbar -lfoo\n" \
- pkgconf --static --libs-only-l foo bar
-}
-
-foo_metapackage_3_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lbar -lfoo\n" \
- pkgconf --static --libs-only-l foo metapackage-3
-}
-
-libs_static2_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -o inline:"-lbar -lbar-private -L/test/lib -lfoo\n" \
- pkgconf --static --libs static-libs
-}
-
-missing_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --cflags missing-require
-}
-
-requires_internal_body()
-{
- atf_check \
- -o inline:"-lbar -lbar-private -L/test/lib -lfoo\n" \
- pkgconf --with-path="${selfdir}/lib1" --static --libs requires-internal
-}
-
-requires_internal_missing_body()
-{
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --with-path="${selfdir}/lib1" --static --libs requires-internal-missing
-}
-
-requires_internal_collision_body()
-{
- atf_check \
- -o inline:"-I/test/local/include/foo\n" \
- pkgconf --with-path="${selfdir}/lib1" --cflags requires-internal-collision
-}
-
-orphaned_requires_private_body()
-{
- atf_check \
- -s exit:1 \
- -e ignore \
- -o ignore \
- pkgconf --with-path="${selfdir}/lib1" --cflags --libs orphaned-requires-private
-}
-
-cflags_libs_private_body()
-{
- atf_check \
- -o inline:"\n" \
- pkgconf --with-path="${selfdir}/lib1" --libs cflags-libs-private-a
-
- atf_check \
- -o inline:"-lc\n" \
- pkgconf --with-path="${selfdir}/lib1" --static --libs cflags-libs-private-a
-}
diff --git a/tests/symlink.sh b/tests/symlink.sh
deleted file mode 100755
index 7f49655755eb..000000000000
--- a/tests/symlink.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- pcfiledir_symlink_absolute \
- pcfiledir_symlink_relative
-
-# - We need to create a temporary subtree, since symlinks are not preserved
-# in "make dist".
-# - ${srcdir} is relative and since we need to compare paths, we would have
-# to portably canonicalize it again, which is hard. Instead, just keep
-# the whole thing nested.
-pcfiledir_symlink_absolute_body()
-{
- mkdir -p tmp/child
- cp -f "${selfdir}/lib1/pcfiledir.pc" tmp/child/
- ln -f -s "${PWD}/tmp/child/pcfiledir.pc" tmp/pcfiledir.pc # absolute
- ln -f -s tmp/pcfiledir.pc pcfiledir.pc
-
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir.pc
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix tmp/pcfiledir.pc
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix tmp/child/pcfiledir.pc
-
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix "${PWD}/pcfiledir.pc"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix "${PWD}/tmp/pcfiledir.pc"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix "${PWD}/tmp/child/pcfiledir.pc"
-
- export PKG_CONFIG_PATH="."
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
- export PKG_CONFIG_PATH="${PWD}"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
-
- export PKG_CONFIG_PATH="tmp"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
- export PKG_CONFIG_PATH="${PWD}/tmp"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
-
- export PKG_CONFIG_PATH="tmp/child"
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
- export PKG_CONFIG_PATH="${PWD}/tmp/child"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
-}
-
-pcfiledir_symlink_relative_body()
-{
- mkdir -p tmp/child
- cp -f "${selfdir}/lib1/pcfiledir.pc" tmp/child/
- ln -f -s child/pcfiledir.pc tmp/pcfiledir.pc # relative
- ln -f -s tmp/pcfiledir.pc pcfiledir.pc
-
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix pcfiledir.pc
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix tmp/pcfiledir.pc
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix tmp/child/pcfiledir.pc
-
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix "${PWD}/pcfiledir.pc"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix "${PWD}/tmp/pcfiledir.pc"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix "${PWD}/tmp/child/pcfiledir.pc"
-
- export PKG_CONFIG_PATH="."
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
- export PKG_CONFIG_PATH="${PWD}"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
-
- export PKG_CONFIG_PATH="tmp"
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
- export PKG_CONFIG_PATH="${PWD}/tmp"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
-
- export PKG_CONFIG_PATH="tmp/child"
- atf_check \
- -o inline:"tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
- export PKG_CONFIG_PATH="${PWD}/tmp/child"
- atf_check \
- -o inline:"${PWD}/tmp/child\n" \
- pkgconf --variable=prefix pcfiledir
-}
diff --git a/tests/sysroot.sh b/tests/sysroot.sh
deleted file mode 100755
index 9d61eaea311c..000000000000
--- a/tests/sysroot.sh
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- cflags \
- variable \
- do_not_eat_slash \
- do_not_duplicate_sysroot_dir \
- uninstalled \
- uninstalled_pkgconf1 \
- uninstalled_fdo \
- uninstalled_fdo_pc_sysrootdir
-
-do_not_eat_slash_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="/"
- atf_check \
- -o inline:"-fPIC -I/test/include/foo\n" \
- pkgconf --cflags baz
-}
-
-cflags_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="${SYSROOT_DIR}"
- atf_check \
- -o inline:"-fPIC -I${SYSROOT_DIR}/test/include/foo\n" \
- pkgconf --cflags baz
-}
-
-variable_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="${SYSROOT_DIR}"
- atf_check \
- -o inline:"${SYSROOT_DIR}/test\n" \
- pkgconf --variable=prefix foo
- atf_check \
- -o inline:"${SYSROOT_DIR}/test/include\n" \
- pkgconf --variable=includedir foo
-}
-
-do_not_duplicate_sysroot_dir_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="/sysroot"
-
- atf_check \
- -o inline:"-I/sysroot/usr/include\n" \
- pkgconf --cflags sysroot-dir-2
-
- atf_check \
- -o inline:"-I/sysroot/usr/include\n" \
- pkgconf --cflags sysroot-dir-3
-
- atf_check \
- -o inline:"-I/sysroot/usr/include\n" \
- pkgconf --cflags sysroot-dir-5
-
- export PKG_CONFIG_SYSROOT_DIR="${SYSROOT_DIR}"
-
- atf_check \
- -o inline:"-I${SYSROOT_DIR}/usr/include\n" \
- pkgconf --cflags sysroot-dir-4
-}
-
-uninstalled_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="/sysroot"
-
- atf_check \
- -o inline:"-L/test/lib -lomg\n" \
- pkgconf --libs omg
-}
-
-uninstalled_pkgconf1_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="/sysroot"
- export PKG_CONFIG_PKGCONF1_SYSROOT_RULES="1"
-
- atf_check \
- -o inline:"-L/sysroot/test/lib -lomg\n" \
- pkgconf --libs omg
-}
-
-uninstalled_fdo_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="/sysroot"
- export PKG_CONFIG_FDO_SYSROOT_RULES="1"
-
- atf_check \
- -o inline:"-L/test/lib -lomg\n" \
- pkgconf --libs omg
-}
-
-uninstalled_fdo_pc_sysrootdir_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- export PKG_CONFIG_SYSROOT_DIR="/sysroot"
- export PKG_CONFIG_FDO_SYSROOT_RULES="1"
-
- atf_check \
- -o inline:"-L/sysroot/test/lib -lomg\n" \
- pkgconf --libs omg-sysroot
-}
diff --git a/tests/test-runner.c b/tests/test-runner.c
new file mode 100644
index 000000000000..5fbb426184c6
--- /dev/null
+++ b/tests/test-runner.c
@@ -0,0 +1,1576 @@
+/*
+ * test-runner.c
+ * test harness
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/config.h>
+#include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/stdinc.h>
+#include <tests/win-shim.h>
+#include <sys/types.h>
+#include <cli/core.h>
+#include <cli/getopt_long.h>
+#include <limits.h>
+#include <assert.h>
+
+#ifndef PKGCONF_LITE
+# if !defined(_WIN32) && !defined(__HAIKU__)
+# define PKGCONF_TEST_PLATFORM "unix"
+# elif !defined(_WIN32)
+# define PKGCONF_TEST_PLATFORM "haiku"
+# else
+# define PKGCONF_TEST_PLATFORM "windows"
+# endif
+#else // PKGCONF_LITE
+# define PKGCONF_TEST_PLATFORM "lite"
+#endif // PKGCONF_LITE
+
+static void test_parser_warn(void *p, const char *fmt, ...) PRINTFLIKE(2, 3);
+static void handle_substs(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pwd);
+
+static pkgconf_buffer_t test_fixtures_dir = PKGCONF_BUFFER_INITIALIZER;
+static pkgconf_buffer_t test_tool_dir = PKGCONF_BUFFER_INITIALIZER;
+static bool debug = false;
+
+typedef enum test_match_strategy_
+{
+ MATCH_EXACT = 0,
+ MATCH_PARTIAL,
+ MATCH_EMPTY,
+} pkgconf_test_match_strategy_t;
+
+typedef struct test_case_
+{
+ char *name;
+ char *testfile_dir;
+
+ pkgconf_list_t search_path;
+ pkgconf_buffer_t query;
+
+ pkgconf_list_t expected_stdout;
+ pkgconf_test_match_strategy_t match_stdout;
+
+ pkgconf_list_t expected_stderr;
+ pkgconf_test_match_strategy_t match_stderr;
+
+ pkgconf_buffer_t expected_stdout_file;
+
+ int exitcode;
+ uint64_t wanted_flags;
+
+ pkgconf_list_t env_vars;
+
+ pkgconf_buffer_t want_env_prefix;
+ pkgconf_buffer_t want_variable;
+ pkgconf_buffer_t fragment_filter;
+
+ pkgconf_buffer_t skip_platforms;
+ bool require_utf8_locale;
+
+ pkgconf_list_t define_variables;
+
+ int verbosity;
+
+ pkgconf_buffer_t atleast_version;
+ pkgconf_buffer_t exact_version;
+ pkgconf_buffer_t max_version;
+
+ pkgconf_buffer_t tool;
+ pkgconf_buffer_t tool_args; // TODO: tool-specific flags
+
+ pkgconf_list_t mkdirs;
+#ifndef _WIN32
+ pkgconf_list_t symlinks;
+#endif
+ pkgconf_list_t copies;
+
+#ifndef PKGCONF_LITE
+ pkgconf_buffer_t want_personality;
+#endif
+} pkgconf_test_case_t;
+
+typedef struct test_state_
+{
+ pkgconf_cli_state_t cli_state;
+ const pkgconf_test_case_t *testcase;
+} pkgconf_test_state_t;
+
+typedef struct test_environ_
+{
+ pkgconf_node_t node;
+ char *key;
+ char *value;
+} pkgconf_test_environ_t;
+
+typedef struct test_output_
+{
+ pkgconf_output_t output;
+
+ pkgconf_buffer_t o_stdout;
+ pkgconf_buffer_t o_stderr;
+} pkgconf_test_output_t;
+
+typedef struct test_flag_pair_
+{
+ const char *name;
+ uint64_t flag;
+} pkgconf_test_flag_pair_t;
+
+typedef void (*test_keyword_func_t)(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value);
+
+typedef struct test_keyword_pair_
+{
+ const char *keyword;
+ const test_keyword_func_t func;
+ const ptrdiff_t offset;
+} pkgconf_test_keyword_pair_t;
+
+static void
+test_environment_push(pkgconf_test_case_t *testcase, const char *key, const char *value)
+{
+ pkgconf_test_environ_t *env = calloc(1, sizeof(*env));
+ if (env == NULL)
+ return;
+
+ env->key = strdup(key);
+ env->value = strdup(value);
+ pkgconf_node_insert_tail(&env->node, env, &testcase->env_vars);
+}
+
+static void
+test_environment_free(pkgconf_list_t *env_list)
+{
+ pkgconf_node_t *iter, *iter_next;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(env_list->head, iter_next, iter)
+ {
+ pkgconf_test_environ_t *env = iter->data;
+
+ pkgconf_node_delete(&env->node, env_list);
+
+ free(env->key);
+ free(env->value);
+ free(env);
+ }
+}
+
+static const char *
+environ_lookup_handler(const pkgconf_client_t *client, const char *key)
+{
+ pkgconf_test_state_t *state = client->client_data;
+ pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(state->testcase->env_vars.head, iter)
+ {
+ pkgconf_test_environ_t *env = iter->data;
+
+ if (!strcmp(key, env->key))
+ {
+ char cwd[PATH_MAX] = {0};
+ const char *pwd = getcwd(cwd, sizeof(cwd));
+
+ pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&expanded, PKGCONF_BUFFER_FROM_STR(env->value), pwd);
+
+ free(env->value);
+ env->value = strdup(pkgconf_buffer_str_or_empty(&expanded));
+ pkgconf_buffer_finalize(&expanded);
+
+ return env->value;
+ }
+ }
+
+ return NULL;
+}
+
+#ifndef PKGCONF_LITE
+static bool
+debug_handler(const char *msg, const pkgconf_client_t *client, void *data)
+{
+ (void) client;
+ (void) data;
+ fprintf(stderr, "%s", msg);
+ return true;
+}
+#endif // PKGCONF_LITE
+
+static bool
+error_handler(const char *msg, const pkgconf_client_t *client, void *data)
+{
+ (void) data;
+ pkgconf_test_state_t *state = client->client_data;
+ pkgconf_output_fmt(client->output, state->testcase->wanted_flags & PKG_ERRORS_ON_STDOUT ? PKGCONF_OUTPUT_STDOUT : PKGCONF_OUTPUT_STDERR, "%s", msg);
+ return true;
+}
+
+static bool
+write_handler(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer)
+{
+ pkgconf_test_output_t *out = (pkgconf_test_output_t *) output;
+ pkgconf_buffer_t *dest = stream == PKGCONF_OUTPUT_STDERR ? &out->o_stderr : &out->o_stdout;
+
+ pkgconf_buffer_append(dest, pkgconf_buffer_str(buffer));
+ return true;
+}
+
+static pkgconf_output_t *
+test_output(void)
+{
+ static pkgconf_test_output_t output =
+ {
+ .output.write = write_handler,
+ };
+
+ return &output.output;
+}
+
+static void
+test_output_reset(pkgconf_test_output_t *out)
+{
+ pkgconf_buffer_reset(&out->o_stdout);
+ pkgconf_buffer_reset(&out->o_stderr);
+}
+
+/*
+ * handle_substs: expand %TEST_FIXTURES_DIR%, %DIR_SEP%, and %PWD%
+ * in src into dest. pwd may be NULL, in which case %PWD% is left as-is
+ * (it should only appear in fields that are re-expanded after tmp_dir creation).
+ */
+static void
+handle_substs(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pwd)
+{
+ struct subst_pair
+ {
+ const char *key;
+ const char *value;
+ } subst_pairs[] =
+ {
+ {"%TEST_FIXTURES_DIR%", pkgconf_buffer_str(&test_fixtures_dir)},
+ {"%DIR_SEP%", PKG_CONFIG_PATH_SEP_S},
+ {"%PWD%", pwd != NULL ? pwd : "%PWD%"},
+ };
+
+ pkgconf_buffer_t workbuf_src = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_t workbuf_dest = PKGCONF_BUFFER_INITIALIZER;
+
+ if (!pkgconf_buffer_len(src))
+ return;
+
+ pkgconf_buffer_append(&workbuf_dest, pkgconf_buffer_str(src));
+
+ for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(subst_pairs); i++)
+ {
+ pkgconf_buffer_reset(&workbuf_src);
+ pkgconf_buffer_append(&workbuf_src, pkgconf_buffer_str(&workbuf_dest));
+
+ pkgconf_buffer_reset(&workbuf_dest);
+ pkgconf_buffer_subst(&workbuf_dest, &workbuf_src, subst_pairs[i].key, subst_pairs[i].value);
+ }
+
+ pkgconf_buffer_append(dest, pkgconf_buffer_str(&workbuf_dest));
+
+ pkgconf_buffer_finalize(&workbuf_src);
+ pkgconf_buffer_finalize(&workbuf_dest);
+}
+
+static int
+test_keyword_pair_cmp(const void *key, const void *ptr)
+{
+ const pkgconf_test_keyword_pair_t *pair = ptr;
+ return strcasecmp(key, pair->keyword);
+}
+
+static void
+test_keyword_set_int(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+ (void) keyword;
+ (void) warnprefix;
+
+ int *dest = (int *)((char *) testcase + offset);
+ *dest = atoi(value);
+}
+
+static void
+test_keyword_set_bool(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+ (void) keyword;
+ (void) warnprefix;
+
+ bool *dest = (bool *)((char *) testcase + offset);
+ *dest = !strcasecmp(value, "true");
+}
+
+static void
+test_keyword_set_buffer(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+ (void) keyword;
+ (void) warnprefix;
+
+ pkgconf_buffer_t *dest = (pkgconf_buffer_t *)((char *) testcase + offset);
+ handle_substs(dest, PKGCONF_BUFFER_FROM_STR((char *) value), NULL);
+}
+
+static void
+test_keyword_extend_bufferset(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+ (void) keyword;
+ (void) warnprefix;
+
+ pkgconf_list_t *dest = (pkgconf_list_t *)((char *) testcase + offset);
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+ handle_substs(&buf, PKGCONF_BUFFER_FROM_STR((char *) value), NULL);
+ pkgconf_bufferset_extend(dest, &buf);
+ pkgconf_buffer_finalize(&buf);
+}
+
+static int
+test_flag_pair_cmp(const void *key, const void *ptr)
+{
+ const pkgconf_test_flag_pair_t *pair = ptr;
+ return strcasecmp(key, pair->name);
+}
+
+static const pkgconf_test_flag_pair_t test_flag_pairs[] =
+{
+ {"cflags", PKG_CFLAGS},
+ {"cflags-only-i", PKG_CFLAGS_ONLY_I},
+ {"cflags-only-other", PKG_CFLAGS_ONLY_OTHER},
+ {"debug", PKG_DEBUG},
+ {"define-prefix", PKG_DEFINE_PREFIX},
+ {"digraph", PKG_DIGRAPH},
+ {"dont-define-prefix", PKG_DONT_DEFINE_PREFIX},
+ {"dont-relocate-paths", PKG_DONT_RELOCATE_PATHS},
+ {"dump-license", PKG_DUMP_LICENSE},
+ {"dump-license-file", PKG_DUMP_LICENSE_FILE},
+ {"dump-personality", PKG_DUMP_PERSONALITY},
+ {"dump-source", PKG_DUMP_SOURCE},
+ {"env-only", PKG_ENV_ONLY},
+ {"errors-on-stdout", PKG_ERRORS_ON_STDOUT},
+ {"exists", PKG_EXISTS},
+ {"exists-cflags", PKG_EXISTS_CFLAGS},
+ {"fragment-tree", PKG_FRAGMENT_TREE},
+ {"ignore-conflicts", PKG_IGNORE_CONFLICTS},
+ {"internal-cflags", PKG_INTERNAL_CFLAGS},
+ {"keep-system-cflags", PKG_KEEP_SYSTEM_CFLAGS},
+ {"keep-system-libs", PKG_KEEP_SYSTEM_LIBS},
+ {"libs", PKG_LIBS},
+ {"libs-only-ldpath", PKG_LIBS_ONLY_LDPATH},
+ {"libs-only-libname", PKG_LIBS_ONLY_LIBNAME},
+ {"libs-only-other", PKG_LIBS_ONLY_OTHER},
+ {"link-abi", PKG_LINK_ABI},
+ {"list", PKG_LIST},
+ {"list-package-names", PKG_LIST_PACKAGE_NAMES},
+ {"modversion", PKG_MODVERSION},
+ {"msvc-syntax", PKG_MSVC_SYNTAX},
+ {"newlines", PKG_NEWLINES},
+ {"no-cache", PKG_NO_CACHE},
+ {"no-provides", PKG_NO_PROVIDES},
+ {"no-uninstalled", PKG_NO_UNINSTALLED},
+ {"path", PKG_PATH},
+ {"print-digraph-query-nodes", PKG_PRINT_DIGRAPH_QUERY_NODES},
+ {"print-errors", PKG_PRINT_ERRORS},
+ {"print-provides", PKG_PROVIDES},
+ {"print-requires", PKG_REQUIRES},
+ {"print-requires-private", PKG_REQUIRES_PRIVATE},
+ {"print-variables", PKG_VARIABLES},
+ {"pure", PKG_PURE},
+ {"shared", PKG_SHARED},
+ {"short-errors", PKG_SHORT_ERRORS},
+ {"silence-errors", PKG_SILENCE_ERRORS},
+ {"simulate", PKG_SIMULATE},
+ {"solution", PKG_SOLUTION},
+ {"static", PKG_STATIC},
+ {"uninstalled", PKG_UNINSTALLED},
+ {"validate", PKG_VALIDATE},
+};
+
+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)
+{
+ int i;
+ int flagcount;
+ char **flags = NULL;
+
+ (void) keyword;
+ (void) warnprefix;
+ (void) offset;
+
+ pkgconf_argv_split(value, &flagcount, &flags);
+
+ for (i = 0; i < flagcount; i++)
+ {
+ const char *flag = flags[i];
+ const pkgconf_test_flag_pair_t *pair = bsearch(flag,
+ test_flag_pairs, PKGCONF_ARRAY_SIZE(test_flag_pairs),
+ sizeof(*pair), test_flag_pair_cmp);
+
+ if (pair == NULL)
+ continue;
+
+ testcase->wanted_flags |= pair->flag;
+ }
+
+ pkgconf_argv_free(flags);
+}
+
+static size_t
+prefixed_path_split(const char *text, pkgconf_list_t *dirlist, const char *prefix)
+{
+ size_t count = 0;
+ char *workbuf, *p, *iter;
+
+ if (text == NULL)
+ return 0;
+
+ iter = workbuf = strdup(text);
+ while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL)
+ {
+ pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&pathbuf, prefix);
+ pkgconf_buffer_push_byte(&pathbuf, '/');
+ pkgconf_buffer_append(&pathbuf, p);
+ pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, false);
+ pkgconf_buffer_finalize(&pathbuf);
+
+ count++, iter = NULL;
+ }
+ free(workbuf);
+
+ return count;
+}
+
+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)
+{
+ (void) keyword;
+ (void) warnprefix;
+
+ pkgconf_list_t *dest = (pkgconf_list_t *)((char *) testcase + offset);
+ prefixed_path_split(value, dest, pkgconf_buffer_str(&test_fixtures_dir));
+}
+
+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)
+{
+ (void) keyword;
+ (void) warnprefix;
+
+ pkgconf_test_match_strategy_t *dest = (pkgconf_test_match_strategy_t *)((char *) testcase + offset);
+
+ if (!strcasecmp(value, "partial"))
+ *dest = MATCH_PARTIAL;
+
+ if (!strcasecmp(value, "empty"))
+ *dest = MATCH_EMPTY;
+}
+
+static void
+test_keyword_set_environment(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+ (void) keyword;
+ (void) offset;
+
+ char *eq = strchr(value, '=');
+ if (eq == NULL)
+ {
+ fprintf(stderr, "%s: malformed Environment entry: %s\n", warnprefix, value);
+ return;
+ }
+
+ *eq++ = '\0';
+
+ // store raw, vars are expanded at run time
+ test_environment_push(testcase, value, eq);
+}
+
+#ifdef _WIN32
+static void
+test_keyword_disabled(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+ (void) testcase;
+ (void) keyword;
+ (void) warnprefix;
+ (void) offset;
+ (void) value;
+}
+#endif
+
+static const pkgconf_test_keyword_pair_t test_keyword_pairs[] =
+{
+ {"AtLeastVersion", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, atleast_version)},
+ {"DefineVariable", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, define_variables)},
+ {"Environment", test_keyword_set_environment, offsetof(pkgconf_test_case_t, env_vars)},
+ {"ExactVersion", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, exact_version)},
+ {"ExpectedExitCode", test_keyword_set_int, offsetof(pkgconf_test_case_t, exitcode)},
+ {"ExpectedStderr", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, expected_stderr)},
+ {"ExpectedStdout", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, expected_stdout)},
+ {"ExpectedStdoutFile", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, expected_stdout_file)},
+ {"FragmentFilter", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, fragment_filter)},
+ {"MatchStderr", test_keyword_set_match_strategy, offsetof(pkgconf_test_case_t, match_stderr)},
+ {"MatchStdout", test_keyword_set_match_strategy, offsetof(pkgconf_test_case_t, match_stdout)},
+ {"MaxVersion", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, max_version)},
+ {"PackageSearchPath", test_keyword_set_path_list, offsetof(pkgconf_test_case_t, search_path)},
+ {"Query", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, query)},
+ {"RequireUtf8Locale", test_keyword_set_bool, offsetof(pkgconf_test_case_t, require_utf8_locale)},
+ {"SetupCopy", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, copies)},
+ {"SetupMkdir", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, mkdirs)},
+#ifdef _WIN32
+ {"SetupSymlink", test_keyword_disabled, 0},
+#else
+ {"SetupSymlink", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, symlinks)},
+#endif
+ {"SkipPlatforms", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, skip_platforms)},
+ {"Tool", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, tool)},
+ {"ToolArgs", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, tool_args)},
+ {"VerbosityLevel", test_keyword_set_int, offsetof(pkgconf_test_case_t, verbosity)},
+ {"WantedFlags", test_keyword_set_wanted_flags, offsetof(pkgconf_test_case_t, wanted_flags)},
+ {"WantEnvPrefix", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, want_env_prefix)},
+#ifndef PKGCONF_LITE
+ {"WantPersonality", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, want_personality)},
+#endif
+ {"WantVariable", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, want_variable)},
+};
+
+static void
+test_keyword_set(void *data, const char *warnprefix, const char *keyword, const char *value)
+{
+ pkgconf_test_case_t *testcase = data;
+ const pkgconf_test_keyword_pair_t *pair = bsearch(keyword,
+ test_keyword_pairs, PKGCONF_ARRAY_SIZE(test_keyword_pairs),
+ sizeof(*pair), test_keyword_pair_cmp);
+
+ if (pair == NULL || pair->func == NULL)
+ return;
+
+ pair->func(testcase, warnprefix, keyword, pair->offset, value);
+}
+
+static const pkgconf_parser_operand_func_t test_parser_ops[256] =
+{
+ [':'] = (pkgconf_parser_operand_func_t) test_keyword_set,
+};
+
+static void
+test_parser_warn(void *p, const char *fmt, ...)
+{
+ va_list va;
+
+ (void) p;
+
+ va_start(va, fmt);
+ vfprintf(stderr, fmt, va);
+ va_end(va);
+}
+
+static pkgconf_test_case_t *
+load_test_case(char *testfile)
+{
+ FILE *testf = fopen(testfile, "r");
+ if (testf == NULL)
+ return NULL;
+
+ pkgconf_test_case_t *out = calloc(1, sizeof(*out));
+ if (out == NULL)
+ goto cleanup;
+
+ char *nameptr;
+ if ((nameptr = strrchr(testfile, '/')) != NULL)
+ nameptr++;
+ else
+ nameptr = testfile;
+
+ out->name = strdup(nameptr);
+
+ // store directory containing the test file for ExpectedStdoutFile resolution
+ {
+ char *dirend = strrchr(testfile, '/');
+ if (dirend != NULL)
+ {
+ size_t dirlen = (size_t)(dirend - testfile);
+ out->testfile_dir = calloc(1, dirlen + 1);
+ if (out->testfile_dir != NULL)
+ memcpy(out->testfile_dir, testfile, dirlen);
+ }
+ else
+ out->testfile_dir = strdup(".");
+ }
+
+ pkgconf_parser_parse(testf, out, test_parser_ops, test_parser_warn, testfile);
+
+cleanup:
+ fclose(testf);
+ return out;
+}
+
+// we use a custom personality to ensure the tests are fully hermetic
+static pkgconf_cross_personality_t *
+personality_for_test(const pkgconf_test_case_t *testcase)
+{
+#ifndef PKGCONF_LITE
+ if (pkgconf_buffer_len(&testcase->want_personality))
+ return pkgconf_cross_personality_find(pkgconf_buffer_str(&testcase->want_personality));
+#endif
+
+ pkgconf_cross_personality_t *pers = calloc(1, sizeof(*pers));
+ if (pers == NULL)
+ return NULL;
+
+ pers->name = strdup("test");
+ pkgconf_path_copy_list(&pers->dir_list, &testcase->search_path);
+ pkgconf_path_add("/test/sysroot/include", &pers->filter_includedirs, false);
+ pkgconf_path_add("/test/sysroot/lib", &pers->filter_libdirs, false);
+
+ return pers;
+}
+
+static bool
+report_failure(pkgconf_test_match_strategy_t match, const pkgconf_buffer_t *expected, const pkgconf_buffer_t *actual, const char *buffername)
+{
+ fprintf(stderr,
+ "================================================================================\n"
+ "%s did not%s match:\n"
+ " expected: [%s]\n"
+ " actual: [%s]\n"
+ "================================================================================\n",
+ buffername, match == MATCH_PARTIAL ? " partially" : "",
+ pkgconf_buffer_str_or_empty(expected),
+ pkgconf_buffer_str_or_empty(actual));
+
+ return false;
+}
+
+static bool
+test_match_buffer(pkgconf_test_match_strategy_t match, const pkgconf_buffer_t *expected, const pkgconf_buffer_t *actual, const char *buffername)
+{
+ if (!pkgconf_buffer_len(expected) && match != MATCH_EMPTY)
+ return true;
+
+ if (!pkgconf_buffer_len(actual))
+ {
+ if (match == MATCH_EMPTY)
+ return true;
+
+ return report_failure(match, expected, actual, buffername);
+ }
+
+ if (match == MATCH_PARTIAL)
+ return pkgconf_buffer_contains(actual, expected) ? true : report_failure(match, expected, actual, buffername);
+
+ return pkgconf_buffer_match(actual, expected) ? true : report_failure(match, expected, actual, buffername);
+}
+
+static bool
+read_file_into_buffer(FILE *f, pkgconf_buffer_t *buf)
+{
+ char tmp[4096] = {0};
+ size_t n;
+ while ((n = fread(tmp, 1, sizeof(tmp), f)) > 0)
+ pkgconf_buffer_append_slice(buf, tmp, n);
+
+ if (ferror(f))
+ return false;
+
+ return true;
+}
+
+static bool
+open_file_into_buffer(const char *path, pkgconf_buffer_t *buf)
+{
+ FILE *f = fopen(path, "r");
+ if (f == NULL)
+ return false;
+
+ bool ok = read_file_into_buffer(f, buf);
+ fclose(f);
+ return ok;
+}
+
+static bool
+copy_file(const char *dst, const char *src)
+{
+ FILE *fsrc = fopen(src, "rb");
+ if (!fsrc)
+ return false;
+
+ FILE *fdst = fopen(dst, "wb");
+ if (!fdst)
+ {
+ int errno_save = errno;
+ fclose(fsrc);
+ errno = errno_save;
+ return false;
+ }
+
+ bool ok = true;
+ char buf[4096] = {0};
+ size_t nr;
+ while ((nr = fread(buf, 1, sizeof(buf), fsrc)) > 0)
+ {
+ if (fwrite(buf, 1, nr, fdst) != nr)
+ {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ferror(fsrc) || ferror(fdst))
+ ok = false;
+
+ int errno_save = errno;
+ fclose(fsrc);
+ fclose(fdst);
+ errno = errno_save;
+
+ return ok;
+}
+
+/*
+ * Recursively remove a directory tree.
+ */
+static void
+rmdir_recursive(const char *path)
+{
+ DIR *dir = opendir(path);
+ if (dir == NULL)
+ return;
+
+ struct dirent *ent;
+ while ((ent = readdir(dir)) != NULL)
+ {
+ if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+ continue;
+
+ pkgconf_buffer_t child = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_append(&child, path);
+ pkgconf_buffer_push_byte(&child, '/');
+ pkgconf_buffer_append(&child, ent->d_name);
+
+#ifdef _WIN32
+ if (_access(pkgconf_buffer_str(&child), 0) == 0)
+ {
+ // Get required buffer size
+ int size = MultiByteToWideChar(CP_ACP, 0, pkgconf_buffer_str(&child), -1, NULL, 0);
+
+ // Allocate and convert
+ wchar_t* wide_path = calloc(size, sizeof(wchar_t));
+ assert(wide_path != NULL);
+ MultiByteToWideChar(CP_ACP, 0, pkgconf_buffer_str(&child), -1, wide_path, size);
+
+ DWORD attrs = GetFileAttributesW(wide_path);
+
+ free(wide_path);
+
+ if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
+ rmdir_recursive(pkgconf_buffer_str(&child));
+ else
+ unlink(pkgconf_buffer_str(&child));
+ }
+#else
+ struct stat st;
+ if (lstat(pkgconf_buffer_str(&child), &st) == 0)
+ {
+ if (S_ISDIR(st.st_mode))
+ rmdir_recursive(pkgconf_buffer_str(&child));
+ else
+ unlink(pkgconf_buffer_str(&child));
+ }
+#endif
+
+ pkgconf_buffer_finalize(&child);
+ }
+
+ closedir(dir);
+ rmdir(path);
+}
+
+/*
+ * Recursively make a directory tree.
+ */
+static bool
+mkdir_recursive(const char *path)
+{
+ if (!path)
+ return false;
+
+ const char *tmpstr = NULL;
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ size_t i = 0;
+
+ while (path[i])
+ {
+ // Append characters up to the next separator
+ size_t start = i;
+ while (path[i] && path[i] != '/')
+ i++;
+
+ pkgconf_buffer_append_slice(&buf, path + start, i - start);
+
+ // If we hit a separator, try to create this component
+ if (path[i] == '/')
+ {
+ pkgconf_buffer_push_byte(&buf, '/');
+ tmpstr = pkgconf_buffer_str(&buf);
+ if (tmpstr && mkdir(tmpstr, 0755) != 0 && errno != EEXIST)
+ {
+ pkgconf_buffer_finalize(&buf);
+ return false;
+ }
+ i++; // skip the separator
+ }
+ }
+
+ // Make the final directory (handles paths without trailing separator)
+ tmpstr = pkgconf_buffer_str(&buf);
+ bool ok = true;
+ if (tmpstr && strlen(tmpstr) > 0)
+ {
+ if (mkdir(tmpstr, 0755) != 0 && errno != EEXIST)
+ ok = false;
+ }
+
+ pkgconf_buffer_finalize(&buf);
+ return ok;
+}
+
+// Returns true if we need tmp_dir
+static bool
+needs_tmp_dir(const pkgconf_test_case_t *testcase)
+{
+#ifdef _WIN32
+ return testcase->mkdirs.head != NULL || testcase->copies.head != NULL;
+#else // _WIN32
+ return testcase->mkdirs.head != NULL || testcase->copies.head != NULL || testcase->symlinks.head != NULL;
+#endif // _WIN32
+}
+
+static int
+run_tool(const pkgconf_test_case_t *testcase, pkgconf_buffer_t *o_stdout, pkgconf_buffer_t *o_stderr)
+{
+ (void) o_stderr; // TODO: external tool stderr goes to real stderr for now
+
+ pkgconf_buffer_t cmdbuf = PKGCONF_BUFFER_INITIALIZER;
+
+ // build: <tool-dir>/<tool> <tool_args>
+ if (pkgconf_buffer_len(&test_tool_dir))
+ {
+ pkgconf_buffer_append(&cmdbuf, pkgconf_buffer_str(&test_tool_dir));
+ pkgconf_buffer_push_byte(&cmdbuf, '/');
+ }
+
+ pkgconf_buffer_append(&cmdbuf, pkgconf_buffer_str(&testcase->tool));
+
+ if (pkgconf_buffer_len(&testcase->tool_args))
+ {
+ pkgconf_buffer_append(&cmdbuf, " ");
+ pkgconf_buffer_append(&cmdbuf, pkgconf_buffer_str(&testcase->tool_args));
+ }
+
+ // Inject Environment vars for the child process
+ char tool_cwd[PATH_MAX] = {0};
+ const char *pwd = getcwd(tool_cwd, sizeof(tool_cwd));
+
+ pkgconf_node_t *iter;
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->env_vars.head, iter)
+ {
+ pkgconf_test_environ_t *env = iter->data;
+ pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&expanded, PKGCONF_BUFFER_FROM_STR(env->value), pwd);
+ setenv(env->key, pkgconf_buffer_str_or_empty(&expanded), 1);
+ pkgconf_buffer_finalize(&expanded);
+ }
+
+ FILE *pipe = popen(pkgconf_buffer_str(&cmdbuf), "r");
+ pkgconf_buffer_finalize(&cmdbuf);
+
+ if (pipe == NULL)
+ {
+ fprintf(stderr, "popen failed for tool '%s': %s\n",
+ pkgconf_buffer_str(&testcase->tool), strerror(errno));
+ return -1;
+ }
+
+ bool ok = read_file_into_buffer(pipe, o_stdout);
+ int saved_errno = errno; // pclose() will clobber errno, save it
+ int status = pclose(pipe);
+ if (!ok)
+ {
+ fprintf(stderr, "read failed into buffer for command '%s': %s",
+ pkgconf_buffer_str(&testcase->tool), strerror(saved_errno));
+ return -1;
+ }
+
+ if (status == -1)
+ {
+ fprintf(stderr, "pclose failed for command '%s': %s\n",
+ pkgconf_buffer_str(&testcase->tool), strerror(errno));
+ return -1;
+ }
+
+#if defined(WIFEXITED) && defined(WEXITSTATUS)
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+
+ fprintf(stderr, "command '%s' did not exit normally\n",
+ pkgconf_buffer_str(&testcase->tool));
+ return -1;
+#else
+ return status;
+#endif
+}
+
+/*
+ * Split a bufferset entry on the first space into left and right halves.
+ * Caller must free *left_out and *right_out.
+ */
+static bool
+split_pair(const char *entry, char **left_out, char **right_out)
+{
+ if (entry == NULL)
+ return false;
+
+ const char *sp = strchr(entry, ' ');
+ if (sp == NULL)
+ return false;
+
+ *left_out = pkgconf_strndup(entry, (size_t)(sp - entry));
+ *right_out = strdup(sp + 1);
+ return true;
+}
+
+/*
+ * run_setup: execute mkdirs, copies, and symlinks in order.
+ * Must be called after chdir() into the tmp_dir.
+ */
+static bool
+run_setup(const pkgconf_test_case_t *testcase, const char *pwd)
+{
+ pkgconf_node_t *iter;
+
+ // mkdirs: each entry is a single path, relative to tmp_dir
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->mkdirs.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ pkgconf_buffer_t path = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&path, &set->buffer, pwd);
+
+ bool ok = mkdir_recursive(pkgconf_buffer_str(&path));
+ pkgconf_buffer_finalize(&path);
+
+ if (!ok && errno != EEXIST)
+ {
+ fprintf(stderr, "SetupMkdir: mkdir '%s' failed: %s\n",
+ pkgconf_buffer_str_or_empty(&set->buffer), strerror(errno));
+ return false;
+ }
+ }
+
+ // copies: "src dst", src relative to TEST_FIXTURES_DIR, dst relative to tmp_dir
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->copies.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&expanded, &set->buffer, pwd);
+
+ char *left = NULL, *right = NULL;
+ if (!split_pair(pkgconf_buffer_str(&expanded), &left, &right))
+ {
+ fprintf(stderr, "SetupCopy: malformed entry (expected 'src dst'): %s\n",
+ pkgconf_buffer_str_or_empty(&set->buffer));
+ pkgconf_buffer_finalize(&expanded);
+ return false;
+ }
+ pkgconf_buffer_finalize(&expanded);
+
+ pkgconf_buffer_t srcpath = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_append(&srcpath, pkgconf_buffer_str(&test_fixtures_dir));
+ pkgconf_buffer_push_byte(&srcpath, '/');
+ pkgconf_buffer_append(&srcpath, left);
+
+ bool ok = copy_file(right, pkgconf_buffer_str(&srcpath));
+ pkgconf_buffer_finalize(&srcpath);
+ if (!ok)
+ {
+ fprintf(stderr, "SetupCopy: failed to copy file '%s' to '%s': %s\n", left, right, strerror(errno));
+ free(left);
+ free(right);
+ return false;
+ }
+
+ free(left);
+ free(right);
+ }
+
+#ifndef _WIN32
+ // symlinks: "target linkpath" — both may be relative to tmp_dir or absolute after %PWD% expansion
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->symlinks.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&expanded, &set->buffer, pwd);
+
+ char *target = NULL, *linkpath = NULL;
+ if (!split_pair(pkgconf_buffer_str(&expanded), &target, &linkpath))
+ {
+ fprintf(stderr, "SetupSymlink: malformed entry (expected 'target linkpath'): %s\n",
+ pkgconf_buffer_str_or_empty(&set->buffer));
+ pkgconf_buffer_finalize(&expanded);
+ return false;
+ }
+ pkgconf_buffer_finalize(&expanded);
+
+ unlink(linkpath);
+
+ if (symlink(target, linkpath) != 0)
+ {
+ fprintf(stderr, "SetupSymlink: symlink('%s', '%s') failed: %s\n",
+ target, linkpath, strerror(errno));
+ free(target);
+ free(linkpath);
+ return false;
+ }
+
+ free(target);
+ free(linkpath);
+ }
+#endif // _WIN32
+
+ return true;
+}
+
+static void
+annotate_result(const pkgconf_test_case_t *testcase, int ret, const pkgconf_test_output_t *out)
+{
+ pkgconf_buffer_t search_path_buf = PKGCONF_BUFFER_INITIALIZER;
+ const pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->search_path.head, iter)
+ {
+ const pkgconf_path_t *path = iter->data;
+
+ if (pkgconf_buffer_len(&search_path_buf))
+ pkgconf_buffer_push_byte(&search_path_buf, ' ');
+
+ pkgconf_buffer_append(&search_path_buf, path->path);
+ }
+
+ pkgconf_buffer_t wanted_flags_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(test_flag_pairs); i++)
+ {
+ const pkgconf_test_flag_pair_t *pair = &test_flag_pairs[i];
+
+ if ((testcase->wanted_flags & pair->flag) == pair->flag)
+ {
+ if (pkgconf_buffer_len(&wanted_flags_buf))
+ pkgconf_buffer_push_byte(&wanted_flags_buf, ' ');
+
+ pkgconf_buffer_append(&wanted_flags_buf, pair->name);
+ }
+ }
+
+ pkgconf_buffer_t env_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->env_vars.head, iter)
+ {
+ const pkgconf_test_environ_t *env = iter->data;
+
+ if (pkgconf_buffer_len(&env_buf))
+ pkgconf_buffer_append(&env_buf, "\n ");
+
+ pkgconf_buffer_append_fmt(&env_buf, "%s: %s", env->key, env->value);
+ }
+
+ fprintf(stderr,
+ "--------------------------------------------------------------------------------\n"
+ "search-path: <%s>\n"
+ "wanted-flags: <%s>\n"
+ "environment:\n"
+ " %s\n"
+ "query: [%s]\n"
+ "exit-code: %d\n"
+ "verbosity: %d\n",
+ pkgconf_buffer_str_or_empty(&search_path_buf),
+ pkgconf_buffer_str_or_empty(&wanted_flags_buf),
+ pkgconf_buffer_str_or_empty(&env_buf),
+ pkgconf_buffer_str_or_empty(&testcase->query),
+ ret,
+ testcase->verbosity);
+
+ if (pkgconf_buffer_len(&testcase->tool))
+ fprintf(stderr, "tool: [%s] tool-args: [%s]\n",
+ pkgconf_buffer_str_or_empty(&testcase->tool),
+ pkgconf_buffer_str_or_empty(&testcase->tool_args));
+
+ fprintf(stderr, "stdout: [%s]\n",
+ pkgconf_buffer_str_or_empty(&out->o_stdout));
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stdout.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ fprintf(stderr,
+ "expected-stdout: [%s] (%s)\n",
+ pkgconf_buffer_str_or_empty(&set->buffer),
+ testcase->match_stdout == MATCH_PARTIAL ? "partial" : "exact");
+ }
+
+ if (pkgconf_buffer_len(&testcase->expected_stdout_file))
+ fprintf(stderr, "expected-stdout-file: [%s]\n",
+ pkgconf_buffer_str_or_empty(&testcase->expected_stdout_file));
+
+ fprintf(stderr, "stderr: [%s]\n",
+ pkgconf_buffer_str_or_empty(&out->o_stderr));
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stderr.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ fprintf(stderr,
+ "expected-stderr: [%s] (%s)\n",
+ pkgconf_buffer_str_or_empty(&set->buffer),
+ testcase->match_stderr == MATCH_PARTIAL ? "partial" : "exact");
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->define_variables.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+ fprintf(stderr, "define-variable: [%s]\n", pkgconf_buffer_str_or_empty(&set->buffer));
+ }
+
+ fprintf(stderr,
+ "want-env-prefix: [%s]\n"
+ "fragment-filter: [%s]\n"
+ "--------------------------------------------------------------------------------\n",
+ pkgconf_buffer_str_or_empty(&testcase->want_env_prefix),
+ pkgconf_buffer_str_or_empty(&testcase->fragment_filter));
+
+ pkgconf_buffer_finalize(&search_path_buf);
+ pkgconf_buffer_finalize(&wanted_flags_buf);
+ pkgconf_buffer_finalize(&env_buf);
+}
+
+static bool
+run_test_case(const pkgconf_test_case_t *testcase)
+{
+ bool passed = true;
+
+ const pkgconf_buffer_t *our_platform = PKGCONF_BUFFER_FROM_STR(PKGCONF_TEST_PLATFORM);
+ if (pkgconf_buffer_contains(&testcase->skip_platforms, our_platform))
+ {
+ printf("# test skipped on %s\nSKIP: %s\n",
+ pkgconf_buffer_str(our_platform), testcase->name);
+ return true;
+ }
+
+ if (testcase->require_utf8_locale && !pkgconf_is_locale_utf8())
+ {
+ printf("# test skipped: requires a UTF-8 locale\nSKIP: %s\n", testcase->name);
+ return true;
+ }
+
+ // If the test has setup steps, create a new tmp_dir and chdir into it.
+ char original_cwd[PATH_MAX] = {0};
+ char *tmp_dir = NULL;
+
+ if (getcwd(original_cwd, sizeof(original_cwd)) == NULL)
+ {
+ fprintf(stderr, "FAIL: getcwd failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ if (needs_tmp_dir(testcase))
+ {
+ pkgconf_buffer_t tmp_buf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_append_fmt(&tmp_buf, "%s/pkgconf-test-XXXXXX", original_cwd);
+ tmp_dir = pkgconf_buffer_freeze(&tmp_buf);
+
+ if (mkdtemp(tmp_dir) == NULL)
+ {
+ fprintf(stderr, "FAIL: mkdtemp failed: %s\n", strerror(errno));
+ free(tmp_dir);
+ return false;
+ }
+
+ if (chdir(tmp_dir) != 0)
+ {
+ fprintf(stderr, "FAIL: chdir('%s') failed: %s\n", tmp_dir, strerror(errno));
+ rmdir(tmp_dir);
+ free(tmp_dir);
+ return false;
+ }
+
+ if (!run_setup(testcase, tmp_dir))
+ {
+ fprintf(stderr, "FAIL: %s (setup failed)\n", testcase->name);
+ chdir(original_cwd);
+ rmdir_recursive(tmp_dir);
+ free(tmp_dir);
+ return false;
+ }
+ }
+
+ pkgconf_test_output_t *out = (pkgconf_test_output_t *) test_output();
+ int ret;
+
+ if (pkgconf_buffer_len(&testcase->tool))
+ {
+ ret = run_tool(testcase, &out->o_stdout, &out->o_stderr);
+ }
+ else
+ {
+ pkgconf_cross_personality_t *personality = personality_for_test(testcase);
+ pkgconf_test_state_t state =
+ {
+ .cli_state.want_flags = testcase->wanted_flags,
+ .cli_state.want_env_prefix = pkgconf_buffer_str(&testcase->want_env_prefix),
+ .cli_state.want_variable = pkgconf_buffer_str(&testcase->want_variable),
+ .cli_state.want_fragment_filter = pkgconf_buffer_str(&testcase->fragment_filter),
+ .cli_state.required_module_version = pkgconf_buffer_str(&testcase->atleast_version),
+ .cli_state.required_exact_module_version = pkgconf_buffer_str(&testcase->exact_version),
+ .cli_state.required_max_module_version = pkgconf_buffer_str(&testcase->max_version),
+ .cli_state.verbosity = testcase->verbosity,
+ .testcase = testcase,
+ };
+
+ pkgconf_client_init(&state.cli_state.pkg_client, error_handler, NULL, personality, &state, environ_lookup_handler);
+ pkgconf_client_set_output(&state.cli_state.pkg_client, &out->output);
+
+ pkgconf_node_t *iter;
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->define_variables.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+ pkgconf_tuple_define_global(&state.cli_state.pkg_client, pkgconf_buffer_str_or_empty(&set->buffer));
+ }
+
+ /*
+ * Re-expand Query now that %PWD% is known (if we have a tmp_dir).
+ * For tests without a tmp_dir this is a no-op since %PWD% won't appear.
+ */
+ char query_cwd[PATH_MAX] = {0};
+ const char *query_pwd = getcwd(query_cwd, sizeof(query_cwd));
+
+ pkgconf_buffer_t query_expanded = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&query_expanded, &testcase->query, query_pwd);
+
+ pkgconf_buffer_t arg_buf = PKGCONF_BUFFER_INITIALIZER;
+ int test_argc = 0;
+ char **test_argv = NULL;
+
+ if (pkgconf_buffer_len(&query_expanded))
+ pkgconf_buffer_append_fmt(&arg_buf, "pkgconf %s", pkgconf_buffer_str(&query_expanded));
+ else
+ pkgconf_buffer_append(&arg_buf, "pkgconf");
+
+ pkgconf_argv_split(pkgconf_buffer_str(&arg_buf), &test_argc, &test_argv);
+ pkgconf_buffer_finalize(&arg_buf);
+ pkgconf_buffer_finalize(&query_expanded);
+
+ pkgconf_client_set_warn_handler(&state.cli_state.pkg_client, error_handler, NULL);
+
+#ifndef PKGCONF_LITE
+ if (debug)
+ pkgconf_client_set_trace_handler(&state.cli_state.pkg_client, debug_handler, NULL);
+#endif // PKGCONF_LITE
+
+ ret = pkgconf_cli_run(&state.cli_state, test_argc, test_argv, 1);
+ pkgconf_argv_free(test_argv);
+ }
+
+ if (pkgconf_buffer_len(&out->o_stdout))
+ pkgconf_buffer_trim_byte(&out->o_stdout);
+
+ if (pkgconf_buffer_len(&out->o_stderr))
+ pkgconf_buffer_trim_byte(&out->o_stderr);
+
+ pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stdout.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ char expected_cwd[PATH_MAX] = {0};
+ const char *expected_pwd = getcwd(expected_cwd, sizeof(expected_cwd));
+
+ pkgconf_buffer_t expected_expanded = PKGCONF_BUFFER_INITIALIZER;
+ handle_substs(&expected_expanded, &set->buffer, expected_pwd);
+
+ if (!test_match_buffer(testcase->match_stdout, &expected_expanded, &out->o_stdout, "stdout"))
+ passed = false;
+
+ pkgconf_buffer_finalize(&expected_expanded);
+ }
+
+ // ExpectedStdoutFile: load file relative to the .test file's directory
+ if (pkgconf_buffer_len(&testcase->expected_stdout_file))
+ {
+ pkgconf_buffer_t filepath = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_append(&filepath, testcase->testfile_dir);
+ pkgconf_buffer_push_byte(&filepath, '/');
+ pkgconf_buffer_append(&filepath, pkgconf_buffer_str(&testcase->expected_stdout_file));
+
+ pkgconf_buffer_t file_contents = PKGCONF_BUFFER_INITIALIZER;
+ if (!open_file_into_buffer(pkgconf_buffer_str(&filepath), &file_contents))
+ {
+ fprintf(stderr, "ExpectedStdoutFile: failed to open '%s': %s\n", pkgconf_buffer_str(&filepath), strerror(errno));
+ passed = false;
+ }
+ else
+ {
+ if (pkgconf_buffer_len(&file_contents))
+ pkgconf_buffer_trim_byte(&file_contents);
+
+ if (!test_match_buffer(testcase->match_stdout, &file_contents, &out->o_stdout, "stdout (file)"))
+ passed = false;
+ }
+
+ pkgconf_buffer_finalize(&file_contents);
+ pkgconf_buffer_finalize(&filepath);
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stderr.head, iter)
+ {
+ pkgconf_bufferset_t *set = iter->data;
+
+ if (!test_match_buffer(testcase->match_stderr, &set->buffer, &out->o_stderr, "stderr"))
+ passed = false;
+ }
+
+ if (ret != testcase->exitcode)
+ {
+ fprintf(stderr, "exitcode %d does not match expected %d\n", ret, testcase->exitcode);
+ passed = false;
+ }
+
+ printf("%s: %s\n", passed ? "PASS" : "FAIL", testcase->name);
+
+ if (!passed)
+ annotate_result(testcase, ret, out);
+
+ test_output_reset(out);
+
+ // Restore cwd and clean up tmp_dir if we created one
+ if (tmp_dir && strcmp(tmp_dir, original_cwd) != 0)
+ {
+ chdir(original_cwd);
+ rmdir_recursive(tmp_dir);
+ }
+
+ free(tmp_dir);
+ return passed;
+}
+
+static void
+free_test_case(pkgconf_test_case_t *testcase)
+{
+ pkgconf_bufferset_free(&testcase->define_variables);
+ pkgconf_bufferset_free(&testcase->expected_stderr);
+ pkgconf_bufferset_free(&testcase->expected_stdout);
+ pkgconf_bufferset_free(&testcase->mkdirs);
+#ifndef _WIN32
+ pkgconf_bufferset_free(&testcase->symlinks);
+#endif // _WIN32
+ pkgconf_bufferset_free(&testcase->copies);
+
+ test_environment_free(&testcase->env_vars);
+ pkgconf_path_free(&testcase->search_path);
+
+ pkgconf_buffer_finalize(&testcase->query);
+ pkgconf_buffer_finalize(&testcase->want_env_prefix);
+ pkgconf_buffer_finalize(&testcase->want_variable);
+ pkgconf_buffer_finalize(&testcase->fragment_filter);
+ pkgconf_buffer_finalize(&testcase->skip_platforms);
+ pkgconf_buffer_finalize(&testcase->atleast_version);
+ pkgconf_buffer_finalize(&testcase->exact_version);
+ pkgconf_buffer_finalize(&testcase->max_version);
+ pkgconf_buffer_finalize(&testcase->expected_stdout_file);
+ pkgconf_buffer_finalize(&testcase->tool);
+ pkgconf_buffer_finalize(&testcase->tool_args);
+
+#ifndef PKGCONF_LITE
+ pkgconf_buffer_finalize(&testcase->want_personality);
+#endif
+
+ free(testcase->name);
+ free(testcase->testfile_dir);
+ free(testcase);
+}
+
+static bool
+process_test_case(char *testcase_file)
+{
+ pkgconf_test_case_t *testcase = load_test_case(testcase_file);
+ bool ret;
+
+ if (testcase == NULL)
+ {
+ fprintf(stderr, "test %s failed to load\n", testcase_file);
+ return false;
+ }
+
+ ret = run_test_case(testcase);
+ free_test_case(testcase);
+
+ return ret;
+}
+
+static inline bool
+str_has_suffix(const char *str, const char *suffix)
+{
+ size_t str_len = strlen(str);
+ size_t suf_len = strlen(suffix);
+
+ if (str_len < suf_len)
+ return false;
+
+ return !strncasecmp(str + str_len - suf_len, suffix, suf_len);
+}
+
+static int
+path_sort_cmp(const void *a, const void *b)
+{
+ return strcmp(*(const char **) a, *(const char **) b);
+}
+
+static bool
+process_test_directory(char *dirpath)
+{
+ bool ret = true;
+ DIR *dir = opendir(dirpath);
+ if (dir == NULL)
+ {
+ fprintf(stderr, "failed to open test directory %s\n", dirpath);
+ return false;
+ }
+ char **paths = NULL;
+ size_t numpaths = 0;
+
+ struct dirent *dirent;
+ for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir))
+ {
+ pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append(&pathbuf, dirpath);
+ pkgconf_buffer_push_byte(&pathbuf, '/');
+ pkgconf_buffer_append(&pathbuf, dirent->d_name);
+
+ char *pathstr = pkgconf_buffer_freeze(&pathbuf);
+ if (pathstr == NULL)
+ continue;
+
+ if (!str_has_suffix(pathstr, ".test"))
+ {
+ free(pathstr);
+ continue;
+ }
+
+ paths = pkgconf_reallocarray(paths, ++numpaths, sizeof(void *));
+ paths[numpaths - 1] = pathstr;
+ }
+
+ qsort(paths, numpaths, sizeof(void *), path_sort_cmp);
+
+ for (size_t i = 0; i < numpaths; i++)
+ {
+ char *pathstr = paths[i];
+
+ ret = process_test_case(pathstr);
+ if (!ret)
+ break;
+ }
+
+ for (size_t i = 0; i < numpaths; i++)
+ free(paths[i]);
+
+ free(paths);
+ closedir(dir);
+ return ret;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: test-runner --test-fixtures <path-to-fixtures> [--tool-dir <path>] <path-to-tests>\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret;
+ char *test_fixtures_dir_arg = NULL;
+ char *test_tool_dir_arg = NULL;
+
+ struct pkg_option options[] =
+ {
+ {"test-fixtures", required_argument, NULL, 1},
+ {"debug", no_argument, NULL, 2},
+ {"test-case", required_argument, NULL, 3},
+ {"tool-dir", required_argument, NULL, 4},
+ {NULL, 0, NULL, 0},
+ };
+ char *testcase = NULL;
+
+ while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1)
+ {
+ switch (ret)
+ {
+ case 1:
+ test_fixtures_dir_arg = pkg_optarg;
+ break;
+ case 2:
+ debug = true;
+ break;
+ case 3:
+ testcase = pkg_optarg;
+ break;
+ case 4:
+ test_tool_dir_arg = pkg_optarg;
+ break;
+ }
+ }
+
+ if (test_fixtures_dir_arg == NULL)
+ usage();
+
+ {
+ char test_fixtures_dir_abs[PATH_MAX] = {0};
+ if (!realpath(test_fixtures_dir_arg, test_fixtures_dir_abs))
+ {
+ fprintf(stderr, "realpath failed: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ const pkgconf_buffer_t *test_fixtures_dir_arg_buf = PKGCONF_BUFFER_FROM_STR_NONNULL(test_fixtures_dir_abs);
+ pkgconf_buffer_subst(&test_fixtures_dir, test_fixtures_dir_arg_buf, "\\", "/");
+ }
+
+ if (test_tool_dir_arg != NULL)
+ {
+ const pkgconf_buffer_t *test_tool_dir_arg_buf = PKGCONF_BUFFER_FROM_STR(test_tool_dir_arg);
+ pkgconf_buffer_subst(&test_tool_dir, test_tool_dir_arg_buf, "\\", "/");
+ }
+
+ if (testcase != NULL)
+ return process_test_case(testcase) ? EXIT_SUCCESS : EXIT_FAILURE;
+
+ if (argv[pkg_optind] == NULL)
+ usage();
+
+ return process_test_directory(argv[pkg_optind]) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/tests/test_env.sh.in b/tests/test_env.sh.in
deleted file mode 100644
index d2b6136f18c9..000000000000
--- a/tests/test_env.sh.in
+++ /dev/null
@@ -1,50 +0,0 @@
-srcdir="$(atf_get_srcdir)"
-export PATH="$srcdir/..:${PATH}"
-
-#--- begin windows kludge ---
-# When building with Visual Studio, binaries are in a subdirectory named after the configration...
-# and the configuration is not known unless you're in the IDE, or something.
-# So just guess. This won't work well if you build more than one configuration.
-the_configuration=""
-for configuration in Debug Release RelWithDebInfo
-do
- if test -d "$srcdir/../$configuration"
- then
- if test "$the_configuration" != ""
- then
- echo "test_env.sh: FAIL: more than one configuration found"
- exit 1
- fi
- the_configuration=$configuration
- export PATH="$srcdir/../${configuration}:${PATH}"
- fi
-done
-#--- end kludge ---
-
-selfdir="@abs_top_srcdir@/tests"
-LIBRARY_PATH_ENV="LIBRARY_PATH"
-PATH_SEP=":"
-SYSROOT_DIR="${selfdir}"
-case "$(uname -s)" in
-Haiku) LIBRARY_PATH_ENV="BELIBRARIES";;
-esac
-
-prefix="@prefix@"
-exec_prefix="@exec_prefix@"
-datarootdir="@datarootdir@"
-pcpath="@PKG_DEFAULT_PATH@"
-
-tests_init()
-{
- TESTS="$@"
- export TESTS
- for t ; do
- atf_test_case $t
- done
-}
-
-atf_init_test_cases() {
- for t in ${TESTS}; do
- atf_add_test_case $t
- done
-}
diff --git a/tests/version.sh b/tests/version.sh
deleted file mode 100755
index 0172c0437da6..000000000000
--- a/tests/version.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env atf-sh
-
-. $(atf_get_srcdir)/test_env.sh
-
-tests_init \
- atleast \
- exact \
- max
-
-atleast_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- pkgconf --atleast-version 1.0 foo
- atf_check \
- -s exit:1 \
- pkgconf --atleast-version 2.0 foo
-}
-
-exact_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --exact-version 1.0 foo
- atf_check \
- pkgconf --exact-version 1.2.3 foo
-}
-
-max_body()
-{
- export PKG_CONFIG_PATH="${selfdir}/lib1"
- atf_check \
- -s exit:1 \
- pkgconf --max-version 1.0 foo
- atf_check \
- pkgconf --max-version 2.0 foo
-}
diff --git a/tests/win-shim.h b/tests/win-shim.h
new file mode 100644
index 000000000000..a58c83d794cc
--- /dev/null
+++ b/tests/win-shim.h
@@ -0,0 +1,57 @@
+/*
+ * win-shim.h
+ * Windows function/define shims
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2025 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#ifndef WIN_SHIM_H
+#define WIN_SHIM_H
+
+#ifdef _WIN32
+
+#include <direct.h>
+#include <io.h>
+
+// Shims shared by both MSVC and MSYS2
+#define mkdir(p, m) _mkdir(p)
+#define setenv(n, v, o) _putenv_s(n, v)
+#define unsetenv(n) _putenv_s(n, "")
+
+#ifndef PATH_MAX
+# define PATH_MAX MAX_PATH
+#endif // !PATH_MAX
+
+// MSVC-specific shims
+#ifdef _MSC_VER
+# define getcwd _getcwd
+# define chdir _chdir
+# define rmdir _rmdir
+# define lstat _lstat
+# define unlink _unlink
+# define popen _popen
+# define pclose _pclose
+
+static inline char *
+mkdtemp(char *tmpl)
+{
+ if (_mktemp_s(tmpl, strlen(tmpl) + 1) != 0)
+ return NULL;
+ if (_mkdir(tmpl) != 0)
+ return NULL;
+ return tmpl;
+}
+#endif // _MSC_VER
+
+#endif // _WIN32
+#endif // WIN_SHIM_H