diff options
| author | Pierre Pronchery <khorben@FreeBSD.org> | 2026-06-25 22:30:05 +0000 |
|---|---|---|
| committer | Pierre Pronchery <khorben@FreeBSD.org> | 2026-06-29 05:21:48 +0000 |
| commit | b0fc1b7a2fe8fcb18ee407227591bf25a86ffbaf (patch) | |
| tree | 0cc32f86bf808bcf9d79da95db969cd76f0feadb /tests/api | |
| parent | eaa637963bcfde283b411c14968b7903b02d6aa1 (diff) | |
Diffstat (limited to 'tests/api')
| -rw-r--r-- | tests/api/oom.h | 59 | ||||
| -rw-r--r-- | tests/api/test-oom-spdxtool.c | 160 | ||||
| -rw-r--r-- | tests/api/test-path-utils.c | 152 | ||||
| -rw-r--r-- | tests/api/test-personality.c | 187 | ||||
| -rw-r--r-- | tests/api/test-queue.c | 250 | ||||
| -rw-r--r-- | tests/api/test-version.c | 133 |
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; +} |
