summaryrefslogtreecommitdiff
path: root/tests/api
diff options
context:
space:
mode:
authorPierre Pronchery <khorben@FreeBSD.org>2026-06-25 22:30:05 +0000
committerPierre Pronchery <khorben@FreeBSD.org>2026-06-29 05:21:48 +0000
commitb0fc1b7a2fe8fcb18ee407227591bf25a86ffbaf (patch)
tree0cc32f86bf808bcf9d79da95db969cd76f0feadb /tests/api
parenteaa637963bcfde283b411c14968b7903b02d6aa1 (diff)
Diffstat (limited to 'tests/api')
-rw-r--r--tests/api/oom.h59
-rw-r--r--tests/api/test-oom-spdxtool.c160
-rw-r--r--tests/api/test-path-utils.c152
-rw-r--r--tests/api/test-personality.c187
-rw-r--r--tests/api/test-queue.c250
-rw-r--r--tests/api/test-version.c133
6 files changed, 941 insertions, 0 deletions
diff --git a/tests/api/oom.h b/tests/api/oom.h
new file mode 100644
index 000000000000..2d3818c86b52
--- /dev/null
+++ b/tests/api/oom.h
@@ -0,0 +1,59 @@
+/*
+ * oom.h
+ * Exhaustive allocation-failure test driver, built on the fuzzer/alloc-inject
+ * fault injector.
+ *
+ * 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_OOM_H
+#define TEST_API_OOM_H
+
+#include "test-api.h"
+#include "alloc-inject.h"
+
+/*
+ * Exhaustively fail each successive allocation an expression makes until it can
+ * complete with none failing. Every injected failure must be reported
+ * gracefully (NULL for OOM_TEST_PTR, false for OOM_TEST_BOOL); on the run where
+ * no allocation fails the value succeeds and the loop ends. Under ASAN this
+ * also asserts each partial-construction error path leaks nothing.
+ */
+#define OOM_TEST_PTR(objvar, make_expr, free_stmt) \
+ do { \
+ for (unsigned long _oom_n = 1; ; _oom_n++) \
+ { \
+ alloc_inject_arm(_oom_n); \
+ (objvar) = (make_expr); \
+ bool _oom_f = alloc_inject_fired(); \
+ alloc_inject_disarm(); \
+ if (!_oom_f) { free_stmt; break; } \
+ TEST_ASSERT_NULL(objvar); \
+ free_stmt; \
+ } \
+ } while (0)
+
+#define OOM_TEST_BOOL(make_expr) \
+ do { \
+ for (unsigned long _oom_n = 1; ; _oom_n++) \
+ { \
+ alloc_inject_arm(_oom_n); \
+ bool _oom_ok = (make_expr); \
+ bool _oom_f = alloc_inject_fired(); \
+ alloc_inject_disarm(); \
+ if (!_oom_f) { TEST_ASSERT_TRUE(_oom_ok); break; } \
+ TEST_ASSERT_FALSE(_oom_ok); \
+ } \
+ } while (0)
+
+#endif // TEST_API_OOM_H
diff --git a/tests/api/test-oom-spdxtool.c b/tests/api/test-oom-spdxtool.c
new file mode 100644
index 000000000000..aa3b36c1c013
--- /dev/null
+++ b/tests/api/test-oom-spdxtool.c
@@ -0,0 +1,160 @@
+/*
+ * test-oom.c
+ * Allocation-failure (OOM) tests for spdxtool, using the oom.h injection
+ * harness. For each target, a failure is injected at every successive
+ * allocation; the target must report the error gracefully (NULL) and, under
+ * ASAN, leak nothing on the error path.
+ *
+ * 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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "oom.h"
+#include "core.h"
+#include "software.h"
+#include "simplelicensing.h"
+#include "serialize.h"
+#include "util.h"
+
+static pkgconf_client_t *g_client;
+
+static void
+oom_setup(void)
+{
+ g_client = test_client_new();
+ // Mirror cli/spdxtool/main.c: the constructors read these globals
+ spdxtool_util_set_uri_root(g_client, "https://example.com/test");
+ spdxtool_util_set_uri_separator_colon(g_client, false);
+ spdxtool_util_set_spdx_version(g_client, "3.0.1");
+ spdxtool_util_set_spdx_license(g_client, "CC0-1.0");
+}
+
+/*
+ * ==============================================
+ * core / software / simplelicensing constructors
+ * ==============================================
+ */
+
+static void
+test_oom_agent_new(void)
+{
+ spdxtool_core_agent_t *a;
+ OOM_TEST_PTR(a, spdxtool_core_agent_new(g_client, "_:c1", "Agent Name"), spdxtool_core_agent_free(a));
+}
+
+static void
+test_oom_creation_info_new(void)
+{
+ spdxtool_core_creation_info_t *c;
+ // NULL time exercises the get_current_iso8601_time() strdup path too
+ OOM_TEST_PTR(c, spdxtool_core_creation_info_new(g_client, "agentid", "_:c1", NULL),
+ spdxtool_core_creation_info_free(c));
+}
+
+static void
+test_oom_spdx_document_new(void)
+{
+ spdxtool_core_spdx_document_t *d;
+ OOM_TEST_PTR(d, spdxtool_core_spdx_document_new(g_client, "docid", "_:c1", "agentid"),
+ spdxtool_core_spdx_document_free(d));
+}
+
+static void
+test_oom_sbom_new(void)
+{
+ spdxtool_software_sbom_t *s;
+ OOM_TEST_PTR(s, spdxtool_software_sbom_new(g_client, "sbomid", "_:c1", "build"), spdxtool_software_sbom_free(s));
+}
+
+static void
+test_oom_license_expression_new(void)
+{
+ spdxtool_simplelicensing_license_expression_t *e;
+ OOM_TEST_PTR(e, spdxtool_simplelicensing_licenseExpression_new(g_client, "MIT"),
+ spdxtool_simplelicensing_licenseExpression_free(e));
+}
+
+/*
+ * ======================
+ * serializer value model
+ * ======================
+ */
+
+static void
+test_oom_serialize_constructors(void)
+{
+ spdxtool_serialize_object_list_t *o;
+ OOM_TEST_PTR(o, spdxtool_serialize_object_list_new(), spdxtool_serialize_object_list_free(o));
+
+ spdxtool_serialize_array_t *a;
+ OOM_TEST_PTR(a, spdxtool_serialize_array_new(), spdxtool_serialize_array_free(a));
+
+ spdxtool_serialize_value_t *v;
+ OOM_TEST_PTR(v, spdxtool_serialize_value_string("hello"), spdxtool_serialize_value_free(v));
+ OOM_TEST_PTR(v, spdxtool_serialize_value_int(7), spdxtool_serialize_value_free(v));
+ OOM_TEST_PTR(v, spdxtool_serialize_value_bool(true), spdxtool_serialize_value_free(v));
+ OOM_TEST_PTR(v, spdxtool_serialize_value_null(), spdxtool_serialize_value_free(v));
+}
+
+/*
+ * ============================================================
+ * *_to_object serializers that depend only on their own struct
+ * ============================================================
+ */
+
+static void
+test_oom_to_object(void)
+{
+ spdxtool_serialize_value_t *v;
+
+ spdxtool_core_agent_t *agent = spdxtool_core_agent_new(g_client, "_:c1", "Agent");
+ TEST_ASSERT_NONNULL(agent);
+ OOM_TEST_PTR(v, spdxtool_core_agent_to_object(g_client, agent),
+ spdxtool_serialize_value_free(v));
+ spdxtool_core_agent_free(agent);
+
+ spdxtool_core_creation_info_t *ci =
+ spdxtool_core_creation_info_new(g_client, "agentid", "_:c1", "2020-01-01T00:00:00Z");
+ TEST_ASSERT_NONNULL(ci);
+ OOM_TEST_PTR(v, spdxtool_core_creation_info_to_object(g_client, ci),
+ spdxtool_serialize_value_free(v));
+ spdxtool_core_creation_info_free(ci);
+
+ spdxtool_simplelicensing_license_expression_t *le =
+ spdxtool_simplelicensing_licenseExpression_new(g_client, "MIT");
+ TEST_ASSERT_NONNULL(le);
+ OOM_TEST_PTR(v, spdxtool_simplelicensing_licenseExpression_to_object(g_client, "_:c1", le),
+ spdxtool_serialize_value_free(v));
+ spdxtool_simplelicensing_licenseExpression_free(le);
+}
+
+int
+main(int argc, const char **argv)
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ oom_setup();
+
+ TEST_RUN(basename, test_oom_agent_new);
+ TEST_RUN(basename, test_oom_creation_info_new);
+ TEST_RUN(basename, test_oom_spdx_document_new);
+ TEST_RUN(basename, test_oom_sbom_new);
+ TEST_RUN(basename, test_oom_license_expression_new);
+ TEST_RUN(basename, test_oom_serialize_constructors);
+ TEST_RUN(basename, test_oom_to_object);
+
+ pkgconf_client_free(g_client);
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-path-utils.c b/tests/api/test-path-utils.c
index 519cadb6f639..029f03b052a5 100644
--- a/tests/api/test-path-utils.c
+++ b/tests/api/test-path-utils.c
@@ -105,6 +105,154 @@ test_determine_prefix_logic(void)
pkgconf_buffer_finalize(&buf);
}
+static const char *
+nth_path(const pkgconf_list_t *list, size_t idx)
+{
+ pkgconf_node_t *n;
+ size_t i = 0;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, n)
+ {
+ const pkgconf_path_t *p = n->data;
+
+ if (i++ == idx)
+ return p->path;
+ }
+
+ return NULL;
+}
+
+static void
+test_path_prepend(void)
+{
+ pkgconf_list_t list = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_path_prepend("/a", &list, false);
+ pkgconf_path_prepend("/b", &list, false);
+ pkgconf_path_prepend("/c", &list, false);
+
+ TEST_ASSERT_EQ(list.length, 3);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&list, 0), "/c");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&list, 1), "/b");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&list, 2), "/a");
+
+ pkgconf_path_add("/d", &list, false);
+ TEST_ASSERT_EQ(list.length, 4);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&list, 0), "/c");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&list, 3), "/d");
+
+ pkgconf_path_prepend("/e//f", &list, false);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&list, 0), "/e/f");
+
+ pkgconf_path_free(&list);
+ TEST_ASSERT_EQ(list.length, 0);
+ TEST_ASSERT_NULL(list.head);
+ TEST_ASSERT_NULL(list.tail);
+}
+
+static void
+test_path_prepend_filter(void)
+{
+ pkgconf_list_t list = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_path_prepend(".", &list, true);
+ TEST_ASSERT_EQ(list.length, 1);
+
+ pkgconf_path_prepend(".", &list, true);
+ TEST_ASSERT_EQ(list.length, 1);
+
+ pkgconf_path_prepend("..", &list, true);
+ TEST_ASSERT_EQ(list.length, 2);
+
+ pkgconf_path_prepend(".", &list, false);
+ TEST_ASSERT_EQ(list.length, 3);
+
+ pkgconf_path_free(&list);
+}
+
+static void
+test_path_prepend_list(void)
+{
+ pkgconf_list_t src = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t dst = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_path_add("/s1", &src, false);
+ pkgconf_path_add("/s2", &src, false);
+ pkgconf_path_add("/s3", &src, false);
+
+ pkgconf_path_add("/d1", &dst, false);
+ pkgconf_path_add("/d2", &dst, false);
+
+ pkgconf_path_prepend_list(&dst, &src);
+
+ TEST_ASSERT_EQ(dst.length, 5);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 0), "/s3");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 1), "/s2");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 2), "/s1");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 3), "/d1");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 4), "/d2");
+
+ TEST_ASSERT_EQ(src.length, 3);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&src, 0), "/s1");
+ TEST_ASSERT_TRUE(nth_path(&dst, 2) != nth_path(&src, 0));
+
+ pkgconf_path_free(&dst);
+ TEST_ASSERT_EQ(dst.length, 0);
+
+ TEST_ASSERT_EQ(src.length, 3);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&src, 2), "/s3");
+
+ pkgconf_path_prepend_list(&dst, &src);
+ TEST_ASSERT_EQ(dst.length, 3);
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 0), "/s3");
+ TEST_ASSERT_STRCMP_EQ(nth_path(&dst, 2), "/s1");
+
+ pkgconf_path_free(&src);
+ pkgconf_path_free(&dst);
+}
+
+static bool
+plausible(const char *text)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ bool result;
+
+ pkgconf_buffer_append(&buf, text);
+ result = pkgconf_path_is_plausible(&buf);
+ pkgconf_buffer_finalize(&buf);
+
+ return result;
+}
+
+static void
+test_path_is_plausible(void)
+{
+ pkgconf_buffer_t empty = PKGCONF_BUFFER_INITIALIZER;
+
+ TEST_ASSERT_FALSE(pkgconf_path_is_plausible(NULL));
+ TEST_ASSERT_FALSE(pkgconf_path_is_plausible(&empty));
+ pkgconf_buffer_finalize(&empty);
+
+ TEST_ASSERT_FALSE(plausible(""));
+ TEST_ASSERT_FALSE(plausible(" "));
+ TEST_ASSERT_FALSE(plausible("libfoo"));
+ TEST_ASSERT_FALSE(plausible("foo bar"));
+ TEST_ASSERT_FALSE(plausible("."));
+ TEST_ASSERT_FALSE(plausible(".."));
+
+ TEST_ASSERT_TRUE(plausible("/usr/lib/pkgconfig"));
+ TEST_ASSERT_TRUE(plausible(" /usr/lib"));
+ TEST_ASSERT_TRUE(plausible("./foo"));
+ TEST_ASSERT_TRUE(plausible("../foo"));
+ TEST_ASSERT_TRUE(plausible(".\\foo"));
+ TEST_ASSERT_TRUE(plausible("..\\foo"));
+ TEST_ASSERT_TRUE(plausible("C:/foo"));
+ TEST_ASSERT_TRUE(plausible("C:\\foo"));
+ TEST_ASSERT_TRUE(plausible("relative/path"));
+ TEST_ASSERT_TRUE(plausible("relative\\path"));
+ TEST_ASSERT_TRUE(plausible("Program Files/MySDK"));
+}
+
int
main(int argc, char *argv[])
{
@@ -114,6 +262,10 @@ main(int argc, char *argv[])
TEST_RUN(basename, test_path_find_basename);
TEST_RUN(basename, test_path_trim_basename);
TEST_RUN(basename, test_determine_prefix_logic);
+ TEST_RUN(basename, test_path_prepend);
+ TEST_RUN(basename, test_path_prepend_filter);
+ TEST_RUN(basename, test_path_prepend_list);
+ TEST_RUN(basename, test_path_is_plausible);
return EXIT_SUCCESS;
}
diff --git a/tests/api/test-personality.c b/tests/api/test-personality.c
new file mode 100644
index 000000000000..11d44854411d
--- /dev/null
+++ b/tests/api/test-personality.c
@@ -0,0 +1,187 @@
+/*
+ * test-personality.c
+ * Tests for libpkgconf personality 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.
+ */
+
+#include "test-api.h"
+
+static void
+test_personality_deinit_null(void)
+{
+ // Should not crash
+ pkgconf_cross_personality_deinit(NULL);
+}
+
+static void
+test_personality_default_refcount(void)
+{
+ pkgconf_cross_personality_t *p1 = pkgconf_cross_personality_default();
+ pkgconf_cross_personality_t *p2 = pkgconf_cross_personality_default();
+
+ TEST_ASSERT_NONNULL(p1);
+ TEST_ASSERT_NONNULL(p2);
+ // Both calls must return the same singleton pointer
+ TEST_ASSERT_EQ((long long)(uintptr_t)p1, (long long)(uintptr_t)p2);
+
+ // Refcount is 2 -> first deinit drops it to 1, must not free
+ pkgconf_cross_personality_deinit(p1);
+ // Refcount is 1 -> second deinit drops to 0, frees internal state
+ pkgconf_cross_personality_deinit(p2);
+
+ // Re-initialise from scratch
+ pkgconf_cross_personality_t *p3 = pkgconf_cross_personality_default();
+ TEST_ASSERT_NONNULL(p3);
+ pkgconf_cross_personality_deinit(p3);
+}
+
+#ifndef PKGCONF_LITE
+
+static void
+test_personality_find_invalid_triplet(void)
+{
+ /* "bad triplet!" has a space and '!' — both rejected by valid_triplet,
+ * and the direct-file open will also fail, so result must be NULL. */
+ pkgconf_cross_personality_t *p = pkgconf_cross_personality_find("bad triplet!");
+ TEST_ASSERT_NULL(p);
+}
+
+static void
+test_personality_find_direct_path(void)
+{
+ char path[] = "test-personality-XXXXXX";
+ int fd = mkstemp(path);
+ TEST_ASSERT_TRUE(fd >= 0);
+
+ FILE *f = fdopen(fd, "w");
+ TEST_ASSERT_NONNULL(f);
+
+ fprintf(f,
+ "Triplet: x86_64-linux-musl\n"
+ "DefaultSearchPaths: /usr/lib/pkgconfig:/usr/share/pkgconfig\n"
+ "SysrootDir: /opt/sysroot\n"
+ "SystemIncludePaths: /opt/sysroot/usr/include\n"
+ "SystemLibraryPaths: /opt/sysroot/usr/lib\n"
+ "WantDefaultStatic: true\n"
+ "WantDefaultPure: yes\n");
+ fclose(f);
+
+ pkgconf_cross_personality_t *p = pkgconf_cross_personality_find(path);
+ TEST_ASSERT_NONNULL(p);
+ TEST_ASSERT_STRCMP_EQ(p->name, "x86_64-linux-musl");
+ TEST_ASSERT_NONNULL(p->sysroot_dir);
+ TEST_ASSERT_STRCMP_EQ(p->sysroot_dir, "/opt/sysroot");
+ TEST_ASSERT_TRUE(p->want_default_static);
+ TEST_ASSERT_TRUE(p->want_default_pure);
+ TEST_ASSERT_NONNULL(p->dir_list.head);
+
+ // Exercises the non-default free path
+ pkgconf_cross_personality_deinit(p);
+ unlink(path);
+}
+
+#if !defined(_WIN32) && !defined(__HAIKU__)
+
+static void
+test_personality_find_via_xdg(void)
+{
+ char tmpdir[] = "test-personality-xdg-XXXXXX";
+ char *d = mkdtemp(tmpdir);
+ TEST_ASSERT_NONNULL(d);
+
+ // build: <tmpdir>/pkgconfig/personality.d/
+ char pkgconfig_dir[4096];
+ char personality_d[4096];
+ char file_path[4096];
+
+ snprintf(pkgconfig_dir, sizeof pkgconfig_dir, "%s/pkgconfig", tmpdir);
+ snprintf(personality_d, sizeof personality_d, "%s/pkgconfig/personality.d", tmpdir);
+ snprintf(file_path, sizeof file_path, "%s/pkgconfig/personality.d/xdg-test-triplet.personality", tmpdir);
+
+ TEST_ASSERT_EQ(mkdir(pkgconfig_dir, 0755), 0);
+ TEST_ASSERT_EQ(mkdir(personality_d, 0755), 0);
+
+ FILE *f = fopen(file_path, "w");
+ TEST_ASSERT_NONNULL(f);
+ fprintf(f, "SysrootDir: /xdg/sysroot\n");
+ fclose(f);
+
+ setenv("XDG_DATA_HOME", tmpdir, 1);
+
+ pkgconf_cross_personality_t *p = pkgconf_cross_personality_find("xdg-test-triplet");
+ TEST_ASSERT_NONNULL(p);
+ TEST_ASSERT_NONNULL(p->sysroot_dir);
+ TEST_ASSERT_STRCMP_EQ(p->sysroot_dir, "/xdg/sysroot");
+
+ // Verify it is NOT the default singleton
+ pkgconf_cross_personality_t *def = pkgconf_cross_personality_default();
+ TEST_ASSERT_NE((long long)(uintptr_t)p, (long long)(uintptr_t)def);
+ // Balance the _default() call above
+ pkgconf_cross_personality_deinit(def);
+
+ // Clean up
+ pkgconf_cross_personality_deinit(p);
+ unsetenv("XDG_DATA_HOME");
+
+ unlink(file_path);
+ rmdir(personality_d);
+ rmdir(pkgconfig_dir);
+ rmdir(tmpdir);
+}
+
+#endif // !_WIN32 && !__HAIKU__
+
+static void
+test_personality_find_missing_returns_default(void)
+{
+ // clear XDG / HOME so search goes to known-empty places
+ unsetenv("XDG_DATA_HOME");
+ unsetenv("XDG_DATA_DIRS");
+ unsetenv("HOME");
+
+ pkgconf_cross_personality_t *found = pkgconf_cross_personality_find("nonexistent-triplet-xyzzy");
+ TEST_ASSERT_NONNULL(found);
+
+ // The find internally called _default(); call it ourselves to compare
+ pkgconf_cross_personality_t *def = pkgconf_cross_personality_default();
+ TEST_ASSERT_EQ((long long)(uintptr_t)found, (long long)(uintptr_t)def);
+
+ // Two deinits: one for find's internal _default(), one for ours above
+ pkgconf_cross_personality_deinit(found);
+ pkgconf_cross_personality_deinit(def);
+}
+
+#endif /* !PKGCONF_LITE */
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ // refcount test must run first so the singleton starts at 0
+ TEST_RUN(basename, test_personality_deinit_null);
+ TEST_RUN(basename, test_personality_default_refcount);
+
+#ifndef PKGCONF_LITE
+ TEST_RUN(basename, test_personality_find_invalid_triplet);
+ TEST_RUN(basename, test_personality_find_direct_path);
+#if !defined(_WIN32) && !defined(__HAIKU__)
+ TEST_RUN(basename, test_personality_find_via_xdg);
+#endif
+ TEST_RUN(basename, test_personality_find_missing_returns_default);
+#endif
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-queue.c b/tests/api/test-queue.c
new file mode 100644
index 000000000000..e18903c2fd9d
--- /dev/null
+++ b/tests/api/test-queue.c
@@ -0,0 +1,250 @@
+/*
+ * test-queue.c
+ * Tests for the public libpkgconf queue 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"
+
+#define FIXTURE_DIR "test-queue-pcdir"
+
+static void
+write_pc(const char *name, const char *contents)
+{
+ char path[512];
+ FILE *f;
+
+ snprintf(path, sizeof path, "%s/%s", FIXTURE_DIR, name);
+ f = fopen(path, "wb");
+ TEST_ASSERT_NONNULL(f);
+ fwrite(contents, 1, strlen(contents), f);
+ fclose(f);
+}
+
+static void
+setup_fixtures(void)
+{
+ mkdir(FIXTURE_DIR, 0755);
+ write_pc("qbar.pc", "Name: qbar\nDescription: bar\nVersion: 2.0\n");
+ write_pc("qfoo.pc",
+ "Name: qfoo\nDescription: foo\nVersion: 1.0\nRequires: qbar >= 1.0\n");
+}
+
+static void
+teardown_fixtures(void)
+{
+ remove(FIXTURE_DIR "/qfoo.pc");
+ remove(FIXTURE_DIR "/qbar.pc");
+ rmdir(FIXTURE_DIR);
+}
+
+static pkgconf_client_t *
+fixture_client(void)
+{
+ pkgconf_client_t *client = test_client_new();
+
+ pkgconf_path_free(&client->dir_list);
+ pkgconf_path_add(FIXTURE_DIR, &client->dir_list, false);
+
+ return client;
+}
+
+static size_t
+required_count(const pkgconf_pkg_t *world)
+{
+ size_t n = 0;
+ const pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
+ {
+ n++;
+ }
+
+ return n;
+}
+
+static bool
+contains_dep(const pkgconf_pkg_t *world, const char *name)
+{
+ const pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
+ {
+ const pkgconf_dependency_t *dep = iter->data;
+
+ if (!strcmp(dep->package, name))
+ return true;
+ }
+
+ return false;
+}
+
+static void
+test_queue_validate_success(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_queue_push(&queue, "qfoo");
+ TEST_ASSERT_TRUE(pkgconf_queue_validate(client, &queue, -1));
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+static void
+test_queue_validate_version_satisfied(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_queue_push(&queue, "qbar >= 1.0");
+ TEST_ASSERT_TRUE(pkgconf_queue_validate(client, &queue, -1));
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+static void
+test_queue_validate_missing_package(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_queue_push(&queue, "does-not-exist");
+ TEST_ASSERT_FALSE(pkgconf_queue_validate(client, &queue, -1));
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+static void
+test_queue_validate_unsatisfiable_version(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+
+ pkgconf_queue_push(&queue, "qbar >= 99.0");
+ TEST_ASSERT_FALSE(pkgconf_queue_validate(client, &queue, -1));
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+static void
+test_queue_validate_empty(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+
+ TEST_ASSERT_FALSE(pkgconf_queue_validate(client, &queue, -1));
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+struct apply_state {
+ int calls;
+ size_t deps;
+ bool saw_qfoo;
+ bool saw_qbar;
+ bool retval;
+};
+
+static bool
+apply_cb(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
+{
+ struct apply_state *st = data;
+
+ (void) client;
+ (void) maxdepth;
+
+ st->calls++;
+ st->deps = required_count(world);
+ st->saw_qfoo = contains_dep(world, "qfoo");
+ st->saw_qbar = contains_dep(world, "qbar");
+
+ return st->retval;
+}
+
+static void
+test_queue_apply_success(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+ struct apply_state st = { .retval = true };
+
+ pkgconf_queue_push(&queue, "qfoo");
+ TEST_ASSERT_TRUE(pkgconf_queue_apply(client, &queue, apply_cb, -1, &st));
+
+ TEST_ASSERT_EQ(st.calls, 1);
+ TEST_ASSERT_GE(st.deps, 2);
+ TEST_ASSERT_TRUE(st.saw_qfoo);
+ TEST_ASSERT_TRUE(st.saw_qbar);
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+static void
+test_queue_apply_callback_failure(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+ struct apply_state st = { .retval = false };
+
+ pkgconf_queue_push(&queue, "qfoo");
+ TEST_ASSERT_FALSE(pkgconf_queue_apply(client, &queue, apply_cb, -1, &st));
+ TEST_ASSERT_EQ(st.calls, 1);
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+static void
+test_queue_apply_missing_package(void)
+{
+ pkgconf_client_t *client = fixture_client();
+ pkgconf_list_t queue = PKGCONF_LIST_INITIALIZER;
+ struct apply_state st = { .retval = true };
+
+ pkgconf_queue_push(&queue, "does-not-exist");
+ TEST_ASSERT_FALSE(pkgconf_queue_apply(client, &queue, apply_cb, -1, &st));
+ TEST_ASSERT_EQ(st.calls, 0);
+
+ pkgconf_queue_free(&queue);
+ pkgconf_client_free(client);
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ setup_fixtures();
+
+ TEST_RUN(basename, test_queue_validate_success);
+ TEST_RUN(basename, test_queue_validate_version_satisfied);
+ TEST_RUN(basename, test_queue_validate_missing_package);
+ TEST_RUN(basename, test_queue_validate_unsatisfiable_version);
+ TEST_RUN(basename, test_queue_validate_empty);
+ TEST_RUN(basename, test_queue_apply_success);
+ TEST_RUN(basename, test_queue_apply_callback_failure);
+ TEST_RUN(basename, test_queue_apply_missing_package);
+
+ teardown_fixtures();
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/api/test-version.c b/tests/api/test-version.c
new file mode 100644
index 000000000000..71ed2c1de811
--- /dev/null
+++ b/tests/api/test-version.c
@@ -0,0 +1,133 @@
+/*
+ * test-version.c
+ * Tests for the public libpkgconf version comparison 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
+cmp_lt(const char *a, const char *b)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version(a, b), -1);
+ TEST_ASSERT_EQ(pkgconf_compare_version(b, a), 1);
+}
+
+static void
+cmp_eq(const char *a, const char *b)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version(a, b), 0);
+ TEST_ASSERT_EQ(pkgconf_compare_version(b, a), 0);
+}
+
+static void
+test_version_equal(void)
+{
+ cmp_eq("1.0.0", "1.0.0");
+ cmp_eq("", "");
+ cmp_eq("abc", "abc");
+ cmp_eq("1.0a", "1.0A");
+ cmp_eq("RELEASE", "release");
+}
+
+static void
+test_version_null(void)
+{
+ TEST_ASSERT_EQ(pkgconf_compare_version(NULL, NULL), -1);
+ cmp_lt(NULL, "1.0");
+ cmp_lt(NULL, "");
+}
+
+static void
+test_version_numeric(void)
+{
+ cmp_lt("1.0.0", "1.0.1");
+ cmp_lt("0.9", "1.0");
+ cmp_lt("1.9.9", "2.0.0");
+ cmp_lt("1.9", "1.10");
+ cmp_lt("1.9", "1.100");
+}
+
+static void
+test_version_leading_zeros(void)
+{
+ cmp_eq("1.0", "1.00");
+ cmp_eq("1.7", "1.007");
+ cmp_eq("01.02.03", "1.2.3");
+ cmp_lt("1.0.1", "1.0.10");
+}
+
+static void
+test_version_component_count(void)
+{
+ cmp_lt("1.0", "1.0.1");
+ cmp_lt("1.0", "1.0.0");
+ cmp_lt("1.2.3", "1.2.3.1");
+}
+
+static void
+test_version_separators(void)
+{
+ cmp_eq("1.2.3", "1-2-3");
+ cmp_eq("1.2.3", "1_2_3");
+ cmp_eq("1.2.3", "1:2:3");
+ cmp_eq("1.0", "1...0");
+}
+
+static void
+test_version_tilde(void)
+{
+ cmp_lt("1.0~rc1", "1.0");
+ cmp_lt("1.0~rc1", "1.0~rc2");
+ cmp_lt("1.0~~", "1.0~");
+ cmp_lt("1.0~1", "1.0.1");
+ cmp_lt("1.0.0~beta", "1.0.0");
+}
+
+static void
+test_version_alpha_numeric(void)
+{
+ cmp_lt("1.b", "1.5");
+ cmp_lt("1.0.alpha", "1.0.beta");
+ cmp_lt("1.0alpha", "1.0alphabeta");
+ cmp_lt("1.0", "1.0pre");
+}
+
+static void
+test_version_real_world(void)
+{
+ cmp_lt("2.5.1", "2.9.91");
+ cmp_lt("2.9.91", "3.0.0");
+ cmp_lt("1.0.0~beta", "1.0.0~rc1");
+ cmp_lt("1.0.0~rc1", "1.0.0");
+}
+
+int
+main(int argc, char *argv[])
+{
+ (void) argc;
+ const char *basename = pkgconf_path_find_basename(argv[0]);
+
+ TEST_RUN(basename, test_version_equal);
+ TEST_RUN(basename, test_version_null);
+ TEST_RUN(basename, test_version_numeric);
+ TEST_RUN(basename, test_version_leading_zeros);
+ TEST_RUN(basename, test_version_component_count);
+ TEST_RUN(basename, test_version_separators);
+ TEST_RUN(basename, test_version_tilde);
+ TEST_RUN(basename, test_version_alpha_numeric);
+ TEST_RUN(basename, test_version_real_world);
+
+ return EXIT_SUCCESS;
+}